tracks.js 6.7 KB

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