preprocessor.js 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  1. const options = {
  2. legend: {
  3. position: "nw",
  4. show: true,
  5. noColumns: 2,
  6. },
  7. zoom: {
  8. interactive: true
  9. },
  10. pan: {
  11. interactive: true,
  12. cursor: "move",
  13. frameRate: 60
  14. },
  15. series: {
  16. lines: {
  17. show: true,
  18. lineWidth: 2
  19. }
  20. },
  21. xaxis: {
  22. tickDecimals: 2,
  23. tickSize: 0.01
  24. },
  25. yaxis: {
  26. tickDecimals: 2,
  27. tickSize: 0.01
  28. }
  29. }
  30. window.onload = function() {
  31. const fileSelector = document.getElementById('file-selector')
  32. fileSelector.addEventListener('change', (event) => {
  33. const fileList = event.target.files;
  34. console.log(fileList);
  35. importFiles(fileList)
  36. })
  37. const dropArea = document.getElementsByClassName('content')[0];
  38. dropArea.addEventListener('dragover', (event) => {
  39. event.stopPropagation();
  40. event.preventDefault();
  41. // Style the drag-and-drop as a "copy file" operation.
  42. event.dataTransfer.dropEffect = 'copy';
  43. });
  44. dropArea.addEventListener('drop', (event) => {
  45. event.stopPropagation();
  46. event.preventDefault();
  47. const fileList = event.dataTransfer.files;
  48. console.log(fileList);
  49. importFiles(fileList)
  50. });
  51. }
  52. function importFiles(fileList) {
  53. d = []
  54. plot = $.plot("#placeholder", d, options);
  55. for (const file of fileList) {
  56. if(file.name?.toLowerCase().endsWith(".txt"))
  57. readTxtFile(file)
  58. else if (file.name?.toLowerCase().endsWith(".nmea"))
  59. parseNmeaFile(file)
  60. }
  61. }
  62. function readTxtFile(file) {
  63. const reader = new FileReader()
  64. reader.addEventListener('load', (event) => {
  65. const lines = event.target.result.split('\n')
  66. let result = []
  67. for(const line of lines) {
  68. const fields = line.split(',')
  69. if(fields.length != 8)
  70. continue
  71. result.push({
  72. timestamp: fields[0],
  73. lat: parseFloat(fields[1]),
  74. lng: parseFloat(fields[2]),
  75. alt: parseFloat(fields[3]),
  76. hdop: parseInt(fields[4]),
  77. speed: parseInt(fields[5])
  78. })
  79. }
  80. process(file.name, result)
  81. });
  82. reader.readAsText(file)
  83. }
  84. function parseNmeaFile(file) {
  85. const reader = new FileReader()
  86. reader.addEventListener('load', (event) => {
  87. const lines = event.target.result.split('\n')
  88. let result = []
  89. let ggaObj, oldTime;
  90. for(const line of lines) {
  91. const obj = GPS.Parse(line);
  92. if (obj.type == "RMC") {
  93. if(ggaObj.time != oldTime) {
  94. oldTime = ggaObj.time
  95. result.push({
  96. timestamp: ggaObj.time,
  97. lat: ggaObj.lat,
  98. lng: ggaObj.lon,
  99. alt: ggaObj.alt,
  100. hdop: ggaObj.hdop,
  101. speed: obj.speed
  102. })
  103. }
  104. ggaObj = null
  105. } else if(ggaObj) {
  106. if(ggaObj.time != oldTime) {
  107. oldTime = ggaObj.time
  108. result.push({
  109. timestamp: ggaObj.time,
  110. lat: ggaObj.lat,
  111. lng: ggaObj.lon,
  112. alt: ggaObj.alt,
  113. hdop: ggaObj.hdop,
  114. speed: null
  115. })
  116. }
  117. ggaObj = null
  118. }
  119. if(obj.type == "GGA") {
  120. ggaObj = obj;
  121. }
  122. }
  123. process(file.name, result)
  124. });
  125. reader.readAsText(file)
  126. }
  127. function process(name, markers) {
  128. d.push({
  129. label: `${name} ${markers.length}`,
  130. data: markers.map(m => [m.lat, m.lng])
  131. })
  132. markers = RamerDouglasPeucker2d(markers, 0.00001) //.0001 = 7m
  133. d.push({
  134. label: `RamerDouglasPeucker2d ${markers.length}`,
  135. data: markers.map(m => [m.lat, m.lng])
  136. })
  137. plot.setData(d);
  138. plot.setupGrid(); //only necessary if your new data will change the axes or grid
  139. plot.draw();
  140. }
  141. function perpendicularDistance2d(ptX, ptY, l1x, l1y, l2x, l2y) {
  142. if (l2x == l1x)
  143. {
  144. //vertical lines - treat this case specially to avoid dividing
  145. //by zero
  146. return Math.abs(ptX - l2x);
  147. }
  148. else
  149. {
  150. const slope = ((l2y-l1y) / (l2x-l1x));
  151. const passThroughY = (0-l1x)*slope + l1y;
  152. return (Math.abs((slope * ptX) - ptY + passThroughY)) /
  153. (Math.sqrt(slope*slope + 1));
  154. }
  155. }
  156. function RamerDouglasPeucker2d(pointList, epsilon) {
  157. if (pointList.length < 2)
  158. {
  159. return pointList;
  160. }
  161. // Find the point with the maximum distance
  162. let dmax = 0;
  163. let index = 0;
  164. let totalPoints = pointList.length;
  165. for (let i = 1; i < (totalPoints - 1); i++)
  166. {
  167. let d = perpendicularDistance2d(
  168. pointList[i].lat, pointList[i].lng,
  169. pointList[0].lat, pointList[0].lng,
  170. pointList[totalPoints-1].lat,
  171. pointList[totalPoints-1].lng);
  172. if (d > dmax)
  173. {
  174. index = i;
  175. dmax = d;
  176. }
  177. }
  178. // If max distance is greater than epsilon, recursively simplify
  179. if (dmax >= epsilon)
  180. {
  181. // Recursive call on each 'half' of the polyline
  182. const recResults1 = RamerDouglasPeucker2d(pointList.slice(0, index + 1), epsilon);
  183. const recResults2 = RamerDouglasPeucker2d(pointList.slice(index), epsilon);
  184. // Build the result list
  185. return recResults1.slice(0, recResults1.length - 1).concat(recResults2);
  186. }
  187. else
  188. {
  189. return [pointList[0], pointList[totalPoints-1]];
  190. }
  191. }