123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257 |
- class GPS {
- parse(data) {
- this.data = data;
- this.index = 0;
-
- this.maxTimeDiff = 3600 * 6;
- this.maxDist = 3000;
-
- this.tracks = [];
- let t;
- while(t = gps.next()) {
- if (t.length > 1)
- this.tracks.push(new Track(t));
- }
- this.regions = this.getRegions();
- }
- next() {
- if(this.index >= this.data.length)
- return;
- let points = [];
- while(this.index < this.data.length - 1) {
- const cur = this.data[this.index];
- const nxt = this.data[this.index+1];
-
- cur.timeDiff = Date.parse(nxt.timestamp) / 1000 - Date.parse(cur.timestamp) / 1000;
- cur.distance = distance(cur, nxt);
-
- if(isNaN(cur.distance))
- cur.distance = 0;
-
- if(cur.speed <= 0) {
- cur.speed = cur.distance / cur.timeDiff * 3.6; // km/h
- }
-
- if(cur.timeDiff > this.maxTimeDiff || cur.distance > this.maxDist) {
- cur.distance = null;
- cur.timeDiff = null;
- cur.speed = null;
- break;
- }
-
- points.push(cur);
- this.index++;
- }
- if(this.index == this.data.length - 1) {
- this.data[this.index].distance = 0;
- this.data[this.index].timeDiff = 0;
- this.data[this.index].speed = 0;
- }
- points.push(this.data[this.index]);
- this.index++;
-
- return points;
- }
- getRegions() {
- var trips = [];
- var s = 0;
- for(let i=0; i<=this.tracks.length; i++) {
- while(i < this.tracks.length - 2) {
- const cur = this.tracks[i].info;
- const nxt = this.tracks[i+1].info;
- if((nxt.startTime - cur.endTime) > 3600 * 24 * 7 || distance(cur.center, nxt.center) > 100000) {
- break;
- }
- i++;
- }
-
- let trip = this.tracks.slice(s, i);
- let dist = trip.reduce((sum, p) => p.info.distance + sum, 0);
- if (dist > 30000) {
- trips.push({
- lat: median(trip.map(v => v.info.center.lat)),
- lng: median(trip.map(v => v.info.center.lng)),
- timestamp: median(trip.map(v => v.info.startTime)),
- distance: dist
- });
- }
- s = i;
- }
- return trips;
- }
- getInfo() {
- let i = {
- points: this.data.length,
- distance: this.tracks.reduce((sum, p) => p.info.distance + sum, 0),
- topSpeed: this.tracks.reduce((max, p) => p.info.topSpeed > max ? p.info.topSpeed : max, 0),
- ascend: this.tracks.reduce((sum, p) => p.info.ascend + sum, 0),
- descend: this.tracks.reduce((sum, p) => p.info.descend + sum, 0),
- movTime: this.tracks.reduce((sum, p) => p.info.movTime + sum, 0),
- totalTime: this.tracks.reduce((sum, p) => p.info.totalTime + sum, 0)
- };
- i.avgSpeed = i.distance / i.movTime * 3.6;
- return i;
- }
- }
- let trackId = 0;
- class Track {
- constructor(data) {
- this.data = data;
- this.id = trackId++;
-
- this.deleted = this.clean();
- this.info = this.getInfo();
- }
-
- clean() {
- let last = this.data[this.data.length-2];
- let speed = last.speed, alt = last.alt;
- let res = [];
- for(let i=this.data.length-2; i>=0; i--) {
- let cur = this.data[i];
- let lst = this.data[i+1];
- if(Math.abs(cur.speed - lst.speed) / cur.timeDiff > 10 || Math.abs(cur.speed - speed) > 50) {
- res.push(this.data.splice(i, 1)[0]);
- } else
- speed = speed * 0.9 + cur.speed * 0.1;
- alt = alt * 0.9 + cur.alt * 0.1;
- }
- return res;
- }
- getInfo() {
- let i = {
- id: this.id,
- points: this.data.length,
- color: getRandomColor(this.data[0].timestamp),
- distance: this.data.reduce((sum, p) => p.distance + sum, 0),
- topSpeed: this.data.reduce((max, p) => p.speed > max ? p.speed : max, this.data[0].speed),
- ascend: 0,
- descend: 0,
- startTime: Date.parse(this.data[0].timestamp) / 1000,
- endTime: Date.parse(this.data[this.data.length-1].timestamp) / 1000,
- movTime: this.data.reduce((sum, p) => p.speed > 2 ? p.timeDiff + sum : sum, 0),
- minLat: this.data.reduce((min, p) => p.lat < min ? p.lat : min, this.data[0].lat),
- maxLat: this.data.reduce((max, p) => p.lat > max ? p.lat : max, this.data[0].lat),
- minLng: this.data.reduce((min, p) => p.lng < min ? p.lng : min, this.data[0].lng),
- maxLng: this.data.reduce((max, p) => p.lng > max ? p.lng : max, this.data[0].lng),
-
- };
- for(let n=1; n<this.data.length; n++) {
- const cur = this.data[n].alt;
- const lst = this.data[n-1].alt;
- if(cur > lst)
- i.ascend += cur - lst;
- else
- i.descend += lst - cur;
- }
- i.totalAscend = i.ascend - i.descend;
- i.avgSpeed = i.distance / i.movTime * 3.6;
- i.center = this.data[parseInt(this.data.length/2)];
- i.totalTime = i.endTime - i.startTime;
-
- return i;
- }
-
- }
- function getRandomColor(str) {
- let arr = str.split('');
- let i = Math.abs(arr.reduce(
- (hashCode, currentVal) =>
- (hashCode = currentVal.charCodeAt(0) + (hashCode << 6) + (hashCode << 16) - hashCode),
- 0
- ));
- return "hsl(" + (i * 2 % 360) + ", " + (i * 3 % 60 + 40) + "%, " + (i * 5 % 40 + 30) + "%)";
- }
- function distance (a, b) {
-
- // Convert degrees to radians
- var lat1 = a.lat * Math.PI / 180.0;
- var lon1 = a.lng * Math.PI / 180.0;
-
- var lat2 = b.lat * Math.PI / 180.0;
- var lon2 = b.lng * Math.PI / 180.0;
-
- // radius of earth in metres
- var r = 6378100;
-
- // P
- var rho1 = r * Math.cos(lat1);
- var z1 = r * Math.sin(lat1);
- var x1 = rho1 * Math.cos(lon1);
- var y1 = rho1 * Math.sin(lon1);
-
- // Q
- var rho2 = r * Math.cos(lat2);
- var z2 = r * Math.sin(lat2);
- var x2 = rho2 * Math.cos(lon2);
- var y2 = rho2 * Math.sin(lon2);
-
- // Dot product
- var dot = (x1 * x2 + y1 * y2 + z1 * z2);
- var cos_theta = dot / (r * r);
-
- var theta = Math.acos(cos_theta);
-
- // Distance in Metres
- return r * theta;
- }
- function timeFormat (sec, x = "time") {
- let d = new Date(sec*1000);
- let t = new Date(null,null,null,null,null,sec).toTimeString().match(/\d{2}:\d{2}:\d{2}/)[0];
-
- switch(x) {
- case "time":
- if(sec < 3600*24)
- return t;
- else
- return Math.floor(sec/3600/24) + "d " + t;
- case "datetime":
- return d.toLocaleDateString('de-DE', {
- year: "numeric",
- month: "2-digit",
- day: "2-digit",
- hour: "2-digit",
- minute: "2-digit",
- second: "2-digit",
- });
- case "date":
- return d.toLocaleDateString('de-DE', {
- year: "numeric",
- month: "2-digit",
- day: "2-digit"
- });
- case "short":
- return d.toLocaleDateString('de-DE', {
- year: "numeric",
- month: "2-digit"
- });
- }
- }
- function median(values) {
- values.sort( function(a,b) {return a - b;} );
- var half = Math.floor(values.length/2);
- return values[half];
- }
- function avg (v) {
- return v.reduce((a,b) => a+b, 0)/v.length;
- }
- function smoothOut (vector, variance) {
- var t_avg = avg(vector)*variance;
- var ret = Array(vector.length);
- for (var i = 0; i < vector.length; i++) {
- (function () {
- var prev = i>0 ? ret[i-1] : vector[i];
- var next = i<vector.length ? vector[i] : vector[i-1];
- ret[i] = avg([t_avg, avg([prev, vector[i], next])]);
- })();
- }
- return ret;
- }
|