gpx-processor.test.js 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139
  1. const assert = require('assert');
  2. const { parseAndCompress } = require('../src/utils/gpx-processor');
  3. function makeGPX(points, name = 'Test Track') {
  4. const trkpts = points.map(p => {
  5. const timeTag = p.time ? `<time>${p.time}</time>` : '';
  6. const eleTag = p.ele != null ? `<ele>${p.ele}</ele>` : '';
  7. return `<trkpt lat="${p.lat}" lon="${p.lon}">${eleTag}${timeTag}</trkpt>`;
  8. }).join('\n');
  9. return `<?xml version="1.0"?>
  10. <gpx version="1.1">
  11. <trk>
  12. <name>${name}</name>
  13. <trkseg>
  14. ${trkpts}
  15. </trkseg>
  16. </trk>
  17. </gpx>`;
  18. }
  19. describe('GPX processor', () => {
  20. describe('parseAndCompress', () => {
  21. it('parses a minimal GPX and returns correct structure', async () => {
  22. const gpx = makeGPX([
  23. { lat: 51.5, lon: -0.1, ele: 10, time: '2024-01-01T10:00:00Z' },
  24. { lat: 51.501, lon: -0.1, ele: 11, time: '2024-01-01T10:01:00Z' },
  25. ], 'My Track');
  26. const result = await parseAndCompress(gpx);
  27. assert.strictEqual(result.trackName, 'My Track');
  28. assert.ok(result.segments.length > 0);
  29. assert.ok(result.pointCount >= 2);
  30. assert.ok(result.totalDistance > 0);
  31. });
  32. it('sets trackDate from first point timestamp', async () => {
  33. const gpx = makeGPX([
  34. { lat: 51.5, lon: -0.1, time: '2024-06-15T08:30:00Z' },
  35. { lat: 51.51, lon: -0.1, time: '2024-06-15T09:00:00Z' },
  36. ]);
  37. const result = await parseAndCompress(gpx);
  38. assert.ok(result.trackDate instanceof Date);
  39. assert.strictEqual(result.trackDate.getFullYear(), 2024);
  40. assert.strictEqual(result.trackDate.getMonth(), 5); // June = 5 (0-indexed)
  41. });
  42. it('skips points closer than 2 m', async () => {
  43. // Points almost on top of each other (< 2m apart)
  44. const gpx = makeGPX([
  45. { lat: 51.5000000, lon: -0.1, time: '2024-01-01T10:00:00Z' },
  46. { lat: 51.5000001, lon: -0.1, time: '2024-01-01T10:00:30Z' }, // ~1 cm away
  47. { lat: 51.5000002, lon: -0.1, time: '2024-01-01T10:01:00Z' }, // still < 2m from first
  48. { lat: 51.5010000, lon: -0.1, time: '2024-01-01T10:01:30Z' }, // ~111 m away
  49. ]);
  50. const result = await parseAndCompress(gpx);
  51. // Should keep first, skip tiny points, keep last far point
  52. assert.ok(result.pointCount < 4, `Expected fewer than 4 points, got ${result.pointCount}`);
  53. assert.ok(result.pointCount >= 2);
  54. });
  55. it('skips points with < 30s gap (no sharp turn)', async () => {
  56. // Points 10s apart in a straight line - should be skipped
  57. const gpx = makeGPX([
  58. { lat: 51.500, lon: -0.100, time: '2024-01-01T10:00:00Z' },
  59. { lat: 51.501, lon: -0.100, time: '2024-01-01T10:00:10Z' }, // 10s gap, straight line
  60. { lat: 51.502, lon: -0.100, time: '2024-01-01T10:00:20Z' }, // 10s gap, straight line
  61. { lat: 51.503, lon: -0.100, time: '2024-01-01T10:01:00Z' }, // 40s gap, should keep
  62. ]);
  63. const result = await parseAndCompress(gpx);
  64. // Middle points should be mostly skipped due to < 30s and no sharp turn
  65. assert.ok(result.pointCount <= 3, `Expected ≤3 points, got ${result.pointCount}`);
  66. });
  67. it('keeps points with < 30s gap if sharp turn', async () => {
  68. // U-turn: going north then suddenly south
  69. const gpx = makeGPX([
  70. { lat: 51.500, lon: -0.100, time: '2024-01-01T10:00:00Z' },
  71. { lat: 51.510, lon: -0.100, time: '2024-01-01T10:00:10Z' }, // 10s, going north
  72. { lat: 51.500, lon: -0.100, time: '2024-01-01T10:00:20Z' }, // 10s, sharp turn back south
  73. { lat: 51.490, lon: -0.100, time: '2024-01-01T10:01:00Z' }, // continuing south
  74. ]);
  75. const result = await parseAndCompress(gpx);
  76. // The middle point (apex of U-turn) should be kept
  77. assert.ok(result.pointCount >= 3, `Expected ≥3 points (turn apex kept), got ${result.pointCount}`);
  78. });
  79. it('keeps points at >= 30s intervals regardless of distance', async () => {
  80. const gpx = makeGPX([
  81. { lat: 51.500, lon: -0.100, time: '2024-01-01T10:00:00Z' },
  82. { lat: 51.501, lon: -0.100, time: '2024-01-01T10:00:30Z' }, // exactly 30s
  83. { lat: 51.502, lon: -0.100, time: '2024-01-01T10:01:00Z' }, // 30s later
  84. ]);
  85. const result = await parseAndCompress(gpx);
  86. assert.strictEqual(result.pointCount, 3);
  87. });
  88. it('handles GPX with no timestamps', async () => {
  89. const gpx = makeGPX([
  90. { lat: 51.500, lon: -0.100 },
  91. { lat: 51.510, lon: -0.100 },
  92. { lat: 51.520, lon: -0.100 },
  93. ]);
  94. const result = await parseAndCompress(gpx);
  95. assert.ok(result.pointCount >= 2);
  96. assert.strictEqual(result.trackDate, null);
  97. });
  98. it('calculates total distance correctly', async () => {
  99. // Two points ~111m apart (0.001 degree lat)
  100. const gpx = makeGPX([
  101. { lat: 51.5000, lon: 0, time: '2024-01-01T10:00:00Z' },
  102. { lat: 51.5010, lon: 0, time: '2024-01-01T10:01:00Z' },
  103. ]);
  104. const result = await parseAndCompress(gpx);
  105. assert.ok(result.totalDistance > 100 && result.totalDistance < 120,
  106. `Expected ~111 m, got ${result.totalDistance.toFixed(1)} m`);
  107. });
  108. it('handles multiple segments', async () => {
  109. const xml = `<?xml version="1.0"?>
  110. <gpx version="1.1">
  111. <trk>
  112. <name>Multi Segment</name>
  113. <trkseg>
  114. <trkpt lat="51.500" lon="0.000"><time>2024-01-01T10:00:00Z</time></trkpt>
  115. <trkpt lat="51.510" lon="0.000"><time>2024-01-01T10:01:00Z</time></trkpt>
  116. </trkseg>
  117. <trkseg>
  118. <trkpt lat="52.000" lon="0.000"><time>2024-01-01T11:00:00Z</time></trkpt>
  119. <trkpt lat="52.010" lon="0.000"><time>2024-01-01T11:01:00Z</time></trkpt>
  120. </trkseg>
  121. </trk>
  122. </gpx>`;
  123. const result = await parseAndCompress(xml);
  124. assert.strictEqual(result.segments.length, 2);
  125. assert.ok(result.pointCount >= 4);
  126. });
  127. });
  128. });