var ds = ""; function dataURL(s) { return ds + s; } function htmlURL(s) { return s; } function error(sts) { var err = document.getElementById("error"); if(err) err.innerHTML = "Error "+sts; } // mk 1 caller has called dataURL // cls is the javascript class // must have constructor that takes json as only argument function loadJSON(url, cls, error) { var arr = []; var xmlhttp = new XMLHttpRequest(); xmlhttp.open("GET", url, false); xmlhttp.send(); if (xmlhttp.status == 200) { // result is JSON array of ids var result = JSON.parse(xmlhttp.responseText); function process(value) { arr.push(new cls(value)); } result.forEach(process); return arr; } if(error) error(xmlhttp.status); return null; } function loadRawJSON(url,error) { var arr = []; var xmlhttp = new XMLHttpRequest(); xmlhttp.open("GET", url, false); xmlhttp.send(); if (xmlhttp.status == 200) { var result = JSON.parse(xmlhttp.responseText); return result; } if(error) error(xmlhttp.status); return ""; } function getIgnition(status) { var sts; // inputs use negative logic 1 for off 0 for on if (status.length == 1) { // old format status is a single hex digit // (ignition on off is in 4th bit) sts = parseInt(status, 16) >> 3; } else { // new format status is a 2 or more hex digits // ignition on off is lowest bit sts = parseInt(status, 16) & 1; } // return 1 for on 0 for off if(sts == 1) { return 0; } return 1; } function getInputs(status) { var sts; // inputs use negative logic 1 for off 0 for on // 2 parts // move the inputs to the low order bits // invert logic back to normal if (status.length == 1) { // old format status is a single hex digit // inputs are in low order 3 bits (ignition on off is in 4th bit which we ignore sts = parseInt(status, 16) & 7; } else { // new format status is a 2 or more hex digits // ignition on off is lowest bit - all other bits are inputs // right shift 1 to discard ignition bit var sts = parseInt(status, 16) >> 1; } // turn back to normal logic by xoring with all bits set // mask bits depends on length of status // 4 bits per hex digit - but bit 0 not used // number of bits is 4n -1 3,7,11... // mask is (2**(4n)-1)-1 7,127,2047... sts ^= ((1 << (status.length *4)-1)-1); return sts; } // Classes and constuctors for above class Org { constructor(json) { this.oid = json.OrganisationID; this.name = json.Organisation_Name; } } // from machine class Vehicle { constructor(json) { this.mid = json.MachineID; this.oid = json.OwnerOrganisationRef; this.name = json.Model; // rest is temporary this.linecolor = '#ff00ff'; this.daytrips = []; // is an array of Trip this.trips = {}; // sequence -> trip lookup table this.alldaytrip; // transient this.currentLocation; // A Loc this.locs = []; this.events = []; } clearData() { this.daytrips = []; this.trips = {}; } setCurrent(loc) { this.currentLocation = loc; } getCurrent() { return this.currentLocation; } processLocData(loc, index, array) { let point = loc.point; var trip; if(index != 0) { trip = this.daytrips[this.daytrips.length -1]; } if(index == 0) // first point of first trip { this.alldaytrip = new Trip(this); this.alldaytrip.Title("All Day"); trip = new Trip(this); this.daytrips.push(trip); trip.Start(loc); this.alldaytrip.Start(loc); } else if(loc.seq != trip.seq) // new trip { // end previous trip trip.End(); // discard if only had 1 point if(trip.count == 1) this.daytrips.pop(); else //keep this.trips[trip.seq] = trip; // uncomment to include red markers in alldaytrip //alldaytrip.End(); // start a new trip (with same vehicle) trip = new Trip(this); this.daytrips.push(trip); trip.Start(loc); // if including red markers have green as well //alldaytrip.Start(loc); // and comment next line this.alldaytrip.Update(loc); } else { trip.Update(loc); this.alldaytrip.Update(loc); } if (index == array.length -1) { trip.End(); // discard if only had 1 point if(trip.count == 1) this.daytrips.pop(); else //keep this.trips[trip.seq] = trip; this.alldaytrip.End(); } } addAllEvents(ev, index, array) { this.alldaytrip.AddEvent(ev); // now add to the individual trip if found // we remove trips is vehicle didnt move // but there will still be events // they are only displayed in the all day trip var trip = this.trips[ev.seq]; if(trip) { //found trip.AddEvent(ev); } } createTrips() { var trip; this.locs.forEach(this.processLocData, this); if(this.events.length != 0) { this.events.forEach(this.addAllEvents, this); } this.daytrips.push(this.alldaytrip); } loadData(ctx) { // alert("Load Trip Data "+this.daytrips.length); if(this.daytrips.length == 0) // not already loaded { var url = dataURL("db?t=daytrips&vid="+ctx.vid()+"&day="+ctx.sqlday()); this.locs = loadJSON(url, Loc, ctx.setError); if(this.locs.length != 0) { var loc = this.locs[0]; // load events url = dataURL("db?t=daytripevents&vid="+loc.id+"&sn="+loc.sn+"&day="+ctx.sqlday()); this.events = loadJSON(url, Event, ctx.setError); // now process into trips this.createTrips(); // alert("Trips "+ this.daytrips.length); } } } } // a and b are javascript Date objects function dateDiffInDays(a, b) { const _MS_PER_DAY = 1000 * 60 * 60 * 24; // Discard the time and time-zone information. const utc1 = Date.UTC(a.getFullYear(), a.getMonth(), a.getDate()); const utc2 = Date.UTC(b.getFullYear(), b.getMonth(), b.getDate()); return Math.floor((utc2 - utc1) / _MS_PER_DAY); } class NavContext { constructor(org) { this.orgid = org; this.orgName = this.loadOrgName(); this.init(); } loadOrgName() { var url = dataURL("db?t=orgname&id="+this.orgid); var result = loadJSON(url, Org, this.setError); return result[0].name; } init() { this.date = new Date(); // date we are displaying - today this.error = ""; this.vehicles = []; // 0 indexed array of Vehicles this.idvehicles = []; // vid indexed array of Vehicles this.vindex = 0; // currently selected vehicle this.vnames = []; //this.vid = 0; // from vehicles[vindex] this.loadVehicles(); } setError(sts) { this.error = "Failed "+sts; } clearError() { this.error = ""; } loadVehicleName(v) { this.vnames[v.mid] = v.name; this.idvehicles[v.mid] = v; } loadVehicles() { var url = dataURL("db?t=vehicles&id="+this.orgid); this.vehicles = loadJSON(url, Vehicle, this.setError); this.vehicles.forEach(this.loadVehicleName,this); } setCurrent(loc) { this.idvehicles[loc.id].setCurrent(loc); } loadCurrent() { var url = dataURL("db?t=last&id="+ctx.orgid); var lastlocs = loadJSON(url, Loc, this.setError); lastlocs.forEach(this.setCurrent,this); } vid() { return this.vehicles[this.vindex].mid; } vname() { if(this.vindex >= 0) return this.vehicles[this.vindex].name; alert("Index "+this.vindex); return "???"; } vehicle() { return this.vehicles[this.vindex]; } changeVehicle(index) { // if(index != this.vindex) { this.vindex = index; this.vehicle().loadData(this); // } } sqlday() { var today = new Date(); return dateDiffInDays(this.date,today); } localDate() { // ISO date is for UTC // so need to sutract timezone offset from date // to get local date let localdate = new Date(this.date.getTime() -(this.date.getTimezoneOffset() * 60*1000)); return localdate.toISOString().substring(0,10); } clear(vehicle) { vehicle.clearData(this); } clearVehicleData() { this.vehicles.forEach(this.clear); } setDate(d) { this.date = d; if(this.sqlday() < 0) { this.date = new Date(); } this.clearVehicleData(); this.changeVehicle(this.vindex); return this.sqlday(); } // set the sqlday for database access // and return the local timezone date in ISO format navDate(days) { var sqlday = this.sqlday(); let date = new Date(); if(sqlday > 0 || days < 0) { sqlday-= days; if (sqlday < 0) sqlday = 0; date.setDate(date.getDate()-sqlday); var newday = this.setDate(date); if(newday != sqlday) alert("SQLDAY="+sqlday+",newday="+newday); } return this.localDate(); } } // from device class IPAddress { constructor(json) { this.sn = json.navtrakka_SN; this.ip = json.navtrakka_IP; this.vid = json.navtrakka_MachineRef; this.oid = json.navtrakka_OrganisationRefl; } } class Loc { constructor(json) { this.id = json.vehicleid; this.sn = json.sn; this.seq = json.seq; this.status = json.status; this.speed = json.speed; this.ts = json.utctime; this.lat = json.lat; this.lon = json.lon; this.point = new ol.geom.Point([json.lon, json.lat]); } getIgnition() { var sts; // inputs use negative logic 1 for off 0 for on if (this.status.length == 1) { // old format status is a single hex digit // (ignition on off is in 4th bit) sts = parseInt(this.status, 16) >> 3; } else { // new format status is a 2 or more hex digits // ignition on off is lowest bit sts = parseInt(this.status, 16) & 1; } // return 1 for on 0 for off if(sts == 1) { return 0; } return 1; } getIgnitionString() { if(this.getIgnition()== 1) return "ignitionOn"; return "ignitionOff"; } } class Event { constructor(json) { this.id = json.vehicleid; this.sn = json.sn; this.seq = json.seq; this.status = json.status; this.ts = json.utctime; this.event = json.event; } } class StreetLoc { constructor(point,name,speedlimit) { this.point = point; this.name = name; this.speedlimit = speedlimit; } } // a track consists of Features // which can be repeated // start (geometry point) // line LineString // inputs (geometry point) // end (geometry point) var gpserror=12.0; // +/-meters function ColorFromSpeed(loc) { let speed = Number(loc.speed); let c = "#cc3232"; // over 110 if (speed < 110) c = "#db7b2b"; if (speed < 100) c = "#e7b416"; if (speed < 80) c = "#99c140"; if (speed < 60) c = "#2dc937"; return c; } class Trip { constructor(vehicle) { this.vehicle = vehicle; this.title = ""; this.start = 0; this.end = 0; this.seq = 0; this.track = []; this.eventhtml = ""; this.user = "Not Recorded"; this.count = 0; // number of points this.ucount = 0; this.snaptrack = []; this.snapradius = ""; this.osmr = ""; } Title(t) { this.title = t; } getTitle() { return this.title ? this.title : displayTime(this.start.ts) + " to "+displayTime(this.end.ts); } AddEvent(ev) { // add the headings if(this.eventhtml == "") { this.eventhtml = "