223 lines
6.5 KiB
JavaScript
223 lines
6.5 KiB
JavaScript
import BratislavaOpendata from "./BratislavaOpendata.js";
|
|
import config from "../config.js";
|
|
import {bearing, removeMilis} from "./math.js";
|
|
import ImhdClient from "./ImhdClient.js";
|
|
import Recorder from "./Recorder.js";
|
|
import getDatabase from "./getDatabase.js";
|
|
|
|
export default class MhdMapApp {
|
|
|
|
constructor(websockets) {
|
|
this.opendata = new BratislavaOpendata(config.openDataKey);
|
|
this.websockets = websockets;
|
|
|
|
this.stops = null;
|
|
this.oldVehicles = null;
|
|
this.vehicles = null;
|
|
this.lastUpdatedStatic = null;
|
|
this.lastUpdatedDynamic = null;
|
|
this.lastDelta = null;
|
|
|
|
this.imhd = new ImhdClient();
|
|
this.recorder = new Recorder(getDatabase());
|
|
}
|
|
|
|
async start() {
|
|
await this.recorder.start();
|
|
|
|
this.websockets.on('join', ws => {
|
|
this.sendAction(ws, 'updateStops', this.stops);
|
|
this.sendAction(ws, 'updateVehicles', this.vehicles);
|
|
});
|
|
this.websockets.on('message', this._onWsMessage.bind(this));
|
|
|
|
setInterval(() => {
|
|
this.updateStatic();
|
|
}, 1000 * 60 * 60 * 24);
|
|
|
|
await this.updateStatic();
|
|
await this.updateDynamic();
|
|
|
|
setInterval(async () => {
|
|
await this.updateDynamic();
|
|
if (this.lastDelta.length > 0) {
|
|
this.broadcastAction('updateVehicles', this.lastDelta);
|
|
}
|
|
}, 1000 * 5);
|
|
|
|
}
|
|
|
|
async updateStatic() {
|
|
let tmp = await this.opendata.fetchAllStops();
|
|
if (tmp) {
|
|
this.stops = tmp;
|
|
this.lastUpdatedStatic = new Date();
|
|
}
|
|
}
|
|
|
|
async updateDynamic() {
|
|
let tmp = await this.opendata.fetchAllVehicles();
|
|
if (!tmp)
|
|
return;
|
|
|
|
// TODO: We're in Slovakia.
|
|
tmp = tmp.filter(el => el.gpsLongitude > 10);
|
|
tmp = this._vehiclesToObj(tmp);
|
|
|
|
this.oldVehicles = this.vehicles;
|
|
this.vehicles = tmp;
|
|
this.lastUpdatedDynamic = new Date();
|
|
if (this.oldVehicles) {
|
|
this.lastDelta = this.calculateVehiclesDelta(this.oldVehicles, this.vehicles);
|
|
this.vehicles = this.addRotationToVehicles(this.oldVehicles, this.vehicles);
|
|
|
|
for (let veh of Object.values(this.lastDelta)) {
|
|
await this.recorder.putVehicle(veh);
|
|
}
|
|
} else {
|
|
for (let veh of Object.values(this.vehicles)) {
|
|
await this.recorder.putVehicle(veh);
|
|
}
|
|
}
|
|
|
|
this.vehicles = this.addImhdDataToVehicles(this.vehicles);
|
|
|
|
let down = 0;
|
|
for (let vehId in this.vehicles) {
|
|
if (await this.imhd.downloadVehicleIcon(vehId) === true) {
|
|
if (down++ === 100) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
calculateVehiclesDelta(beforeObj, afterObj) {
|
|
let delta = {};
|
|
|
|
for (let vehId in beforeObj) {
|
|
let beforeVeh = beforeObj[vehId];
|
|
if (afterObj.hasOwnProperty(vehId)) {
|
|
let afterVeh = afterObj[vehId];
|
|
if (!this.isVehicleObjectEqual(beforeVeh, afterVeh)) {
|
|
delta[vehId] = afterVeh;
|
|
}
|
|
} else {
|
|
// removed vehicle
|
|
beforeVeh.gpsLatitude = -1000;
|
|
beforeVeh.gpsLongitude = -1000;
|
|
delta[vehId] = beforeVeh;
|
|
console.log('vehicle left', beforeVeh);
|
|
}
|
|
}
|
|
|
|
for (let vehId in afterObj) {
|
|
if (!beforeObj.hasOwnProperty(vehId)) {
|
|
// new vehicle
|
|
delta[vehId] = afterObj[vehId];
|
|
// console.log('new vehicle joined', afterObj[vehId]);
|
|
}
|
|
}
|
|
|
|
return delta;
|
|
}
|
|
|
|
_vehiclesToObj(arr) {
|
|
let obj = {};
|
|
|
|
for (let vehicle of arr) {
|
|
let id = this._vehicleToId(vehicle);
|
|
vehicle.lastModified = removeMilis(new Date(vehicle.lastModified));
|
|
|
|
if (obj.hasOwnProperty(id)) {
|
|
console.log('DUPLICATE vehicle?!', 'old:', obj[id], 'new:', vehicle);
|
|
}
|
|
obj[id] = vehicle;
|
|
}
|
|
|
|
return obj;
|
|
}
|
|
|
|
_vehicleToId(vehicle) {
|
|
return vehicle.vehicleNumber;
|
|
}
|
|
|
|
isVehicleObjectEqual(a, b, checkLastModified = false) {
|
|
return (
|
|
a.vehicleNumber === b.vehicleNumber
|
|
&& a.lineNumber === b.lineNumber
|
|
&& a.gpsLatitude === b.gpsLatitude
|
|
&& a.gpsLongitude === b.gpsLongitude
|
|
&& (!checkLastModified || (a.lastModified === b.lastModified))
|
|
);
|
|
}
|
|
|
|
isVehiclePositionEqual(a, b) {
|
|
return a.gpsLatitude === b.gpsLatitude && a.gpsLongitude === b.gpsLongitude;
|
|
}
|
|
|
|
addImhdDataToVehicles(vehicles) {
|
|
for (let vehId in vehicles) {
|
|
const data = this.imhd.getVehicleData(vehId);
|
|
if (!data)
|
|
continue;
|
|
|
|
let vehicle = vehicles[vehId];
|
|
vehicle.img = data.img;
|
|
vehicle.type = data.type;
|
|
vehicle.imhdinfo = data.info;
|
|
}
|
|
return vehicles;
|
|
}
|
|
|
|
addRotationToVehicles(before, after) {
|
|
for (let vehId in after) {
|
|
if (before.hasOwnProperty(vehId)) {
|
|
after[vehId].bearing = bearing(before[vehId], after[vehId]);
|
|
}
|
|
}
|
|
return after;
|
|
}
|
|
|
|
broadcastAction(action, data) {
|
|
this.websockets.broadcast(JSON.stringify({ action, data }));
|
|
}
|
|
|
|
sendAction(ws, action, data) {
|
|
ws.send(JSON.stringify({ action, data }));
|
|
}
|
|
|
|
async _onWsMessage(ws, data) {
|
|
// Note: before sending the response, it should be checked if this ws is still open
|
|
try {
|
|
data = JSON.parse(data);
|
|
} catch (e) {
|
|
console.log('Malformed request: ', data, e);
|
|
return null;
|
|
}
|
|
|
|
const msgid = data.id;
|
|
|
|
let res = await this.handleWsRequest(ws, data.action, data.msg);
|
|
|
|
if (ws.readyState !== 1) { // 1 = WebSocket.OPEN
|
|
// closed while handling the request
|
|
return;
|
|
}
|
|
|
|
this.sendAction(ws, 'response', {
|
|
id: msgid,
|
|
msg: res,
|
|
});
|
|
}
|
|
|
|
async handleWsRequest(ws, action, data) {
|
|
if (action === 'requestVehicleTrace') {
|
|
return await this.recorder.getVehicleTrace(data.vehicle, data.count || 20);
|
|
} else if (action === 'requestLineTrace') {
|
|
return await this.recorder.getSimpleLineTrace(data.line, data.count || 20);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
} |