227 lines
12 KiB
HTML
227 lines
12 KiB
HTML
<!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>
|
|
|
|
<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>
|
|
|
|
<script>
|
|
// map the field to coordinates of our dummy tracker
|
|
// see test_homography.ipynb for the logic behind these values
|
|
const field_range = { x: [-13.092, 15.37], y: [-4.66, 10.624] }
|
|
|
|
// 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);
|
|
});
|
|
prediction_socket.addEventListener("open", (e) => appendAndSendPositions());
|
|
|
|
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]
|
|
|
|
const x = Array.isArray(position) ? position[0] : position.x;
|
|
const y = Array.isArray(position) ? position[1] : position.y;
|
|
return {
|
|
x: (x - field_range.x[0]) * fieldEl.width / x_range,
|
|
y: (y - field_range.y[0]) * fieldEl.width / y_range,
|
|
}
|
|
}
|
|
|
|
// 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);
|
|
}
|
|
}
|
|
|
|
let current_pos = null;
|
|
|
|
|
|
function appendAndSendPositions() {
|
|
if (is_moving && current_pos !== null) {
|
|
// throttled update of tracker on movement
|
|
tracker[person_counter].addToHistory(current_pos);
|
|
}
|
|
|
|
for (const person_id in tracker) {
|
|
if (person_id != person_counter) { // compare int/str
|
|
// fade out old tracks
|
|
tracker[person_id].history.shift()
|
|
if (!tracker[person_id].history.length) {
|
|
delete tracker[person_id]
|
|
}
|
|
}
|
|
}
|
|
|
|
// TODO: make variable in template
|
|
//trajectory_socket.send(JSON.stringify(tracker))
|
|
|
|
setTimeout(appendAndSendPositions, 200)
|
|
}
|
|
|
|
fieldEl.addEventListener('mousedown', (event) => {
|
|
tracker[person_counter] = new Person(person_counter);
|
|
is_moving = true;
|
|
|
|
const mousePos = getMousePos(fieldEl, event);
|
|
const position = mouse_coordinates_to_position(mousePos)
|
|
current_pos = position;
|
|
tracker[person_counter].addToHistory(current_pos);
|
|
// 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)
|
|
current_pos = position;
|
|
tracker[person_counter].addToHistory(current_pos);
|
|
trajectory_socket.send(JSON.stringify(tracker))
|
|
});
|
|
document.addEventListener('mouseup', (e) => {
|
|
person_counter++;
|
|
is_moving = false;
|
|
})
|
|
|
|
const ctx = fieldEl.getContext("2d");
|
|
function drawFrame() {
|
|
ctx.clearRect(0, 0, fieldEl.width, fieldEl.height);
|
|
ctx.save();
|
|
|
|
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()
|
|
|
|
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();
|
|
}
|
|
|
|
if (person.hasOwnProperty('predictions') && person.predictions.length > 0) {
|
|
// multiple predictions can be sampled
|
|
person.predictions.forEach((prediction, i) => {
|
|
ctx.beginPath()
|
|
ctx.lineWidth = 0.2;
|
|
ctx.strokeStyle = i === 1 ? "#ff0000" : "#ccaaaa";
|
|
ctx.strokeStyle = "#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();
|
|
});
|
|
|
|
// average stroke:
|
|
ctx.beginPath()
|
|
ctx.lineWidth = 3;
|
|
ctx.strokeStyle = "#ff0000";
|
|
|
|
// start from current position:
|
|
ctx.moveTo(...coord_as_list(position_to_canvas_coordinate(person.history[person.history.length - 1])));
|
|
for (let index = 0; index < person.predictions[0].length; index++) {
|
|
|
|
sum = person.predictions.reduce(
|
|
(accumulator, prediction) => ({
|
|
"x": accumulator.x + prediction[index][0],
|
|
"y": accumulator.y + prediction[index][1],
|
|
}),
|
|
{ x: 0, y: 0 },
|
|
);
|
|
avg = { x: sum.x / person.predictions.length, y: sum.y / person.predictions.length }
|
|
// console.log(sum, avg)
|
|
ctx.lineTo(...coord_as_list(position_to_canvas_coordinate(avg)))
|
|
|
|
|
|
}
|
|
// for (const position of ) {
|
|
// }
|
|
|
|
ctx.stroke();
|
|
}
|
|
}
|
|
ctx.restore();
|
|
|
|
window.requestAnimationFrame(drawFrame);
|
|
}
|
|
|
|
window.requestAnimationFrame(drawFrame);
|
|
</script>
|
|
</body>
|
|
|
|
</html> |