tracks.js 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257
  1. class GPS {
  2. parse(data) {
  3. this.data = data;
  4. this.index = 0;
  5. this.maxTimeDiff = 3600 * 6;
  6. this.maxDist = 3000;
  7. this.tracks = [];
  8. let t;
  9. while(t = gps.next()) {
  10. if (t.length > 1)
  11. this.tracks.push(new Track(t));
  12. }
  13. this.regions = this.getRegions();
  14. }
  15. next() {
  16. if(this.index >= this.data.length)
  17. return;
  18. let points = [];
  19. while(this.index < this.data.length - 1) {
  20. const cur = this.data[this.index];
  21. const nxt = this.data[this.index+1];
  22. cur.timeDiff = Date.parse(nxt.timestamp) / 1000 - Date.parse(cur.timestamp) / 1000;
  23. cur.distance = distance(cur, nxt);
  24. if(isNaN(cur.distance))
  25. cur.distance = 0;
  26. if(cur.speed <= 0) {
  27. cur.speed = cur.distance / cur.timeDiff * 3.6; // km/h
  28. }
  29. if(cur.timeDiff > this.maxTimeDiff || cur.distance > this.maxDist) {
  30. cur.distance = null;
  31. cur.timeDiff = null;
  32. cur.speed = null;
  33. break;
  34. }
  35. points.push(cur);
  36. this.index++;
  37. }
  38. if(this.index == this.data.length - 1) {
  39. this.data[this.index].distance = 0;
  40. this.data[this.index].timeDiff = 0;
  41. this.data[this.index].speed = 0;
  42. }
  43. points.push(this.data[this.index]);
  44. this.index++;
  45. return points;
  46. }
  47. getRegions() {
  48. var trips = [];
  49. var s = 0;
  50. for(let i=0; i<=this.tracks.length; i++) {
  51. while(i < this.tracks.length - 2) {
  52. const cur = this.tracks[i].info;
  53. const nxt = this.tracks[i+1].info;
  54. if((nxt.startTime - cur.endTime) > 3600 * 24 * 7 || distance(cur.center, nxt.center) > 100000) {
  55. break;
  56. }
  57. i++;
  58. }
  59. let trip = this.tracks.slice(s, i);
  60. let dist = trip.reduce((sum, p) => p.info.distance + sum, 0);
  61. if (dist > 30000) {
  62. trips.push({
  63. lat: median(trip.map(v => v.info.center.lat)),
  64. lng: median(trip.map(v => v.info.center.lng)),
  65. timestamp: median(trip.map(v => v.info.startTime)),
  66. distance: dist
  67. });
  68. }
  69. s = i;
  70. }
  71. return trips;
  72. }
  73. getInfo() {
  74. let i = {
  75. points: this.data.length,
  76. distance: this.tracks.reduce((sum, p) => p.info.distance + sum, 0),
  77. topSpeed: this.tracks.reduce((max, p) => p.info.topSpeed > max ? p.info.topSpeed : max, 0),
  78. ascend: this.tracks.reduce((sum, p) => p.info.ascend + sum, 0),
  79. descend: this.tracks.reduce((sum, p) => p.info.descend + sum, 0),
  80. movTime: this.tracks.reduce((sum, p) => p.info.movTime + sum, 0),
  81. totalTime: this.tracks.reduce((sum, p) => p.info.totalTime + sum, 0)
  82. };
  83. i.avgSpeed = i.distance / i.movTime * 3.6;
  84. return i;
  85. }
  86. }
  87. let trackId = 0;
  88. class Track {
  89. constructor(data) {
  90. this.data = data;
  91. this.id = trackId++;
  92. this.deleted = this.clean();
  93. this.info = this.getInfo();
  94. }
  95. clean() {
  96. let last = this.data[this.data.length-2];
  97. let speed = last.speed, alt = last.alt;
  98. let res = [];
  99. for(let i=this.data.length-2; i>=0; i--) {
  100. let cur = this.data[i];
  101. let lst = this.data[i+1];
  102. if(Math.abs(cur.speed - lst.speed) / cur.timeDiff > 10 || Math.abs(cur.speed - speed) > 50) {
  103. res.push(this.data.splice(i, 1)[0]);
  104. } else
  105. speed = speed * 0.9 + cur.speed * 0.1;
  106. alt = alt * 0.9 + cur.alt * 0.1;
  107. }
  108. return res;
  109. }
  110. getInfo() {
  111. let i = {
  112. id: this.id,
  113. points: this.data.length,
  114. color: getRandomColor(this.data[0].timestamp),
  115. distance: this.data.reduce((sum, p) => p.distance + sum, 0),
  116. topSpeed: this.data.reduce((max, p) => p.speed > max ? p.speed : max, this.data[0].speed),
  117. ascend: 0,
  118. descend: 0,
  119. startTime: Date.parse(this.data[0].timestamp) / 1000,
  120. endTime: Date.parse(this.data[this.data.length-1].timestamp) / 1000,
  121. movTime: this.data.reduce((sum, p) => p.speed > 2 ? p.timeDiff + sum : sum, 0),
  122. minLat: this.data.reduce((min, p) => p.lat < min ? p.lat : min, this.data[0].lat),
  123. maxLat: this.data.reduce((max, p) => p.lat > max ? p.lat : max, this.data[0].lat),
  124. minLng: this.data.reduce((min, p) => p.lng < min ? p.lng : min, this.data[0].lng),
  125. maxLng: this.data.reduce((max, p) => p.lng > max ? p.lng : max, this.data[0].lng),
  126. };
  127. for(let n=1; n<this.data.length; n++) {
  128. const cur = this.data[n].alt;
  129. const lst = this.data[n-1].alt;
  130. if(cur > lst)
  131. i.ascend += cur - lst;
  132. else
  133. i.descend += lst - cur;
  134. }
  135. i.totalAscend = i.ascend - i.descend;
  136. i.avgSpeed = i.distance / i.movTime * 3.6;
  137. i.center = this.data[parseInt(this.data.length/2)];
  138. i.totalTime = i.endTime - i.startTime;
  139. return i;
  140. }
  141. }
  142. function getRandomColor(str) {
  143. let arr = str.split('');
  144. let i = Math.abs(arr.reduce(
  145. (hashCode, currentVal) =>
  146. (hashCode = currentVal.charCodeAt(0) + (hashCode << 6) + (hashCode << 16) - hashCode),
  147. 0
  148. ));
  149. return "hsl(" + (i * 2 % 360) + ", " + (i * 3 % 60 + 40) + "%, " + (i * 5 % 40 + 30) + "%)";
  150. }
  151. function distance (a, b) {
  152. // Convert degrees to radians
  153. var lat1 = a.lat * Math.PI / 180.0;
  154. var lon1 = a.lng * Math.PI / 180.0;
  155. var lat2 = b.lat * Math.PI / 180.0;
  156. var lon2 = b.lng * Math.PI / 180.0;
  157. // radius of earth in metres
  158. var r = 6378100;
  159. // P
  160. var rho1 = r * Math.cos(lat1);
  161. var z1 = r * Math.sin(lat1);
  162. var x1 = rho1 * Math.cos(lon1);
  163. var y1 = rho1 * Math.sin(lon1);
  164. // Q
  165. var rho2 = r * Math.cos(lat2);
  166. var z2 = r * Math.sin(lat2);
  167. var x2 = rho2 * Math.cos(lon2);
  168. var y2 = rho2 * Math.sin(lon2);
  169. // Dot product
  170. var dot = (x1 * x2 + y1 * y2 + z1 * z2);
  171. var cos_theta = dot / (r * r);
  172. var theta = Math.acos(cos_theta);
  173. // Distance in Metres
  174. return r * theta;
  175. }
  176. function timeFormat (sec, x = "time") {
  177. let d = new Date(sec*1000);
  178. let t = new Date(null,null,null,null,null,sec).toTimeString().match(/\d{2}:\d{2}:\d{2}/)[0];
  179. switch(x) {
  180. case "time":
  181. if(sec < 3600*24)
  182. return t;
  183. else
  184. return Math.floor(sec/3600/24) + "d " + t;
  185. case "datetime":
  186. return d.toLocaleDateString('de-DE', {
  187. year: "numeric",
  188. month: "2-digit",
  189. day: "2-digit",
  190. hour: "2-digit",
  191. minute: "2-digit",
  192. second: "2-digit",
  193. });
  194. case "date":
  195. return d.toLocaleDateString('de-DE', {
  196. year: "numeric",
  197. month: "2-digit",
  198. day: "2-digit"
  199. });
  200. case "short":
  201. return d.toLocaleDateString('de-DE', {
  202. year: "numeric",
  203. month: "2-digit"
  204. });
  205. }
  206. }
  207. function median(values) {
  208. values.sort( function(a,b) {return a - b;} );
  209. var half = Math.floor(values.length/2);
  210. return values[half];
  211. }
  212. function avg (v) {
  213. return v.reduce((a,b) => a+b, 0)/v.length;
  214. }
  215. function smoothOut (vector, variance) {
  216. var t_avg = avg(vector)*variance;
  217. var ret = Array(vector.length);
  218. for (var i = 0; i < vector.length; i++) {
  219. (function () {
  220. var prev = i>0 ? ret[i-1] : vector[i];
  221. var next = i<vector.length ? vector[i] : vector[i-1];
  222. ret[i] = avg([t_avg, avg([prev, vector[i], next])]);
  223. })();
  224. }
  225. return ret;
  226. }