@ingissa/navcore-react-native 1.0.4 → 1.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (123) hide show
  1. package/LICENSE +3 -0
  2. package/dist/core/src/NavCore.d.ts +142 -0
  3. package/dist/core/src/NavCore.d.ts.map +1 -0
  4. package/dist/core/src/NavCore.js +390 -0
  5. package/dist/core/src/bearing/BearingEngine.d.ts +24 -0
  6. package/dist/core/src/bearing/BearingEngine.d.ts.map +1 -0
  7. package/dist/core/src/bearing/BearingEngine.js +50 -0
  8. package/dist/core/src/constants.d.ts +34 -0
  9. package/dist/core/src/constants.d.ts.map +1 -0
  10. package/dist/core/src/constants.js +41 -0
  11. package/dist/core/src/corridor/DeviationDetector.d.ts +33 -0
  12. package/dist/core/src/corridor/DeviationDetector.d.ts.map +1 -0
  13. package/dist/core/src/corridor/DeviationDetector.js +83 -0
  14. package/dist/core/src/corridor/DynamicCorridor.d.ts +13 -0
  15. package/dist/core/src/corridor/DynamicCorridor.d.ts.map +1 -0
  16. package/dist/core/src/corridor/DynamicCorridor.js +26 -0
  17. package/dist/core/src/dead-reckoning/DeadReckoning.d.ts +35 -0
  18. package/dist/core/src/dead-reckoning/DeadReckoning.d.ts.map +1 -0
  19. package/dist/core/src/dead-reckoning/DeadReckoning.js +49 -0
  20. package/dist/core/src/directions/DirectionsProvider.d.ts +70 -0
  21. package/dist/core/src/directions/DirectionsProvider.d.ts.map +1 -0
  22. package/dist/core/src/directions/DirectionsProvider.js +7 -0
  23. package/dist/core/src/directions/OSRMDirectionsProvider.d.ts +26 -0
  24. package/dist/core/src/directions/OSRMDirectionsProvider.d.ts.map +1 -0
  25. package/dist/core/src/directions/OSRMDirectionsProvider.js +90 -0
  26. package/dist/core/src/directions/OpenRouteServiceProvider.d.ts +30 -0
  27. package/dist/core/src/directions/OpenRouteServiceProvider.d.ts.map +1 -0
  28. package/dist/core/src/directions/OpenRouteServiceProvider.js +111 -0
  29. package/dist/core/src/directions/ValhallaDirectionsProvider.d.ts +33 -0
  30. package/dist/core/src/directions/ValhallaDirectionsProvider.d.ts.map +1 -0
  31. package/dist/core/src/directions/ValhallaDirectionsProvider.js +155 -0
  32. package/dist/core/src/eta/ETAEngine.d.ts +61 -0
  33. package/dist/core/src/eta/ETAEngine.d.ts.map +1 -0
  34. package/dist/core/src/eta/ETAEngine.js +74 -0
  35. package/dist/core/src/features.d.ts +76 -0
  36. package/dist/core/src/features.d.ts.map +1 -0
  37. package/dist/core/src/features.js +76 -0
  38. package/dist/core/src/geo/bearing.d.ts +11 -0
  39. package/dist/core/src/geo/bearing.d.ts.map +1 -0
  40. package/dist/core/src/geo/bearing.js +25 -0
  41. package/dist/core/src/geo/cumulative.d.ts +9 -0
  42. package/dist/core/src/geo/cumulative.d.ts.map +1 -0
  43. package/dist/core/src/geo/cumulative.js +20 -0
  44. package/dist/core/src/geo/haversine.d.ts +13 -0
  45. package/dist/core/src/geo/haversine.d.ts.map +1 -0
  46. package/dist/core/src/geo/haversine.js +39 -0
  47. package/dist/core/src/geo/projection.d.ts +7 -0
  48. package/dist/core/src/geo/projection.d.ts.map +1 -0
  49. package/dist/core/src/geo/projection.js +17 -0
  50. package/dist/core/src/geofencing/GeofencingEngine.d.ts +93 -0
  51. package/dist/core/src/geofencing/GeofencingEngine.d.ts.map +1 -0
  52. package/dist/core/src/geofencing/GeofencingEngine.js +154 -0
  53. package/dist/core/src/index.d.ts +66 -0
  54. package/dist/core/src/index.d.ts.map +1 -0
  55. package/dist/core/src/index.js +62 -0
  56. package/dist/core/src/kalman/KalmanFilter2D.d.ts +46 -0
  57. package/dist/core/src/kalman/KalmanFilter2D.d.ts.map +1 -0
  58. package/dist/core/src/kalman/KalmanFilter2D.js +77 -0
  59. package/dist/core/src/license/LicenseManager.d.ts +40 -0
  60. package/dist/core/src/license/LicenseManager.d.ts.map +1 -0
  61. package/dist/core/src/license/LicenseManager.js +119 -0
  62. package/dist/core/src/license/verifyP256Signature.d.ts +2 -0
  63. package/dist/core/src/license/verifyP256Signature.d.ts.map +1 -0
  64. package/dist/core/src/license/verifyP256Signature.js +26 -0
  65. package/dist/core/src/off-route/NavigationFSM.d.ts +42 -0
  66. package/dist/core/src/off-route/NavigationFSM.d.ts.map +1 -0
  67. package/dist/core/src/off-route/NavigationFSM.js +56 -0
  68. package/dist/core/src/off-route/OffRouteFSM.d.ts +70 -0
  69. package/dist/core/src/off-route/OffRouteFSM.d.ts.map +1 -0
  70. package/dist/core/src/off-route/OffRouteFSM.js +151 -0
  71. package/dist/core/src/predictor/RouteOffsetPredictor.d.ts +24 -0
  72. package/dist/core/src/predictor/RouteOffsetPredictor.d.ts.map +1 -0
  73. package/dist/core/src/predictor/RouteOffsetPredictor.js +55 -0
  74. package/dist/core/src/progress/InstructionEditor.d.ts +77 -0
  75. package/dist/core/src/progress/InstructionEditor.d.ts.map +1 -0
  76. package/dist/core/src/progress/InstructionEditor.js +151 -0
  77. package/dist/core/src/progress/InstructionResolver.d.ts +25 -0
  78. package/dist/core/src/progress/InstructionResolver.d.ts.map +1 -0
  79. package/dist/core/src/progress/InstructionResolver.js +78 -0
  80. package/dist/core/src/progress/ProgressTracker.d.ts +12 -0
  81. package/dist/core/src/progress/ProgressTracker.d.ts.map +1 -0
  82. package/dist/core/src/progress/ProgressTracker.js +22 -0
  83. package/dist/core/src/renderer/MapRendererAdapter.d.ts +72 -0
  84. package/dist/core/src/renderer/MapRendererAdapter.d.ts.map +1 -0
  85. package/dist/core/src/renderer/MapRendererAdapter.js +19 -0
  86. package/dist/core/src/route-builder/CustomRouteBuilder.d.ts +83 -0
  87. package/dist/core/src/route-builder/CustomRouteBuilder.d.ts.map +1 -0
  88. package/dist/core/src/route-builder/CustomRouteBuilder.js +160 -0
  89. package/dist/core/src/route-plan/RoutePlan.d.ts +38 -0
  90. package/dist/core/src/route-plan/RoutePlan.d.ts.map +1 -0
  91. package/dist/core/src/route-plan/RoutePlan.js +102 -0
  92. package/dist/core/src/session/NavigationSession.d.ts +81 -0
  93. package/dist/core/src/session/NavigationSession.d.ts.map +1 -0
  94. package/dist/core/src/session/NavigationSession.js +115 -0
  95. package/dist/core/src/snapper/ParallelRoadResolver.d.ts +86 -0
  96. package/dist/core/src/snapper/ParallelRoadResolver.d.ts.map +1 -0
  97. package/dist/core/src/snapper/ParallelRoadResolver.js +83 -0
  98. package/dist/core/src/snapper/RouteSnapper.d.ts +38 -0
  99. package/dist/core/src/snapper/RouteSnapper.d.ts.map +1 -0
  100. package/dist/core/src/snapper/RouteSnapper.js +80 -0
  101. package/dist/core/src/voice/VoiceTriggerEngine.d.ts +80 -0
  102. package/dist/core/src/voice/VoiceTriggerEngine.d.ts.map +1 -0
  103. package/dist/core/src/voice/VoiceTriggerEngine.js +108 -0
  104. package/dist/react-native/src/NavCoreProvider.d.ts +33 -0
  105. package/dist/react-native/src/NavCoreProvider.d.ts.map +1 -0
  106. package/dist/react-native/src/NavCoreProvider.js +41 -0
  107. package/dist/react-native/src/index.d.ts +10 -0
  108. package/dist/react-native/src/index.d.ts.map +1 -0
  109. package/dist/react-native/src/index.js +8 -0
  110. package/dist/{index.d.mts → react-native/src/useNavCore.d.ts} +12 -39
  111. package/dist/react-native/src/useNavCore.d.ts.map +1 -0
  112. package/dist/react-native/src/useNavCore.js +119 -0
  113. package/dist/react-native/src/useNavSelectors.d.ts +42 -0
  114. package/dist/react-native/src/useNavSelectors.d.ts.map +1 -0
  115. package/dist/react-native/src/useNavSelectors.js +103 -0
  116. package/package.json +20 -12
  117. package/src/NavCoreProvider.tsx +59 -0
  118. package/src/index.ts +18 -0
  119. package/src/useNavCore.ts +208 -0
  120. package/src/useNavSelectors.ts +143 -0
  121. package/dist/index.d.ts +0 -87
  122. package/dist/index.js +0 -161
  123. package/dist/index.mjs +0 -157
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Engine constants — centralized tuning parameters.
3
+ * Extracted from packages/nav-sdk/src/shared/constants.ts
4
+ */
5
+ // ── Corridor ──────────────────────────────────────────────────
6
+ export const BASE_CORRIDOR_METERS = 25;
7
+ export const MAX_CORRIDOR_METERS = 60;
8
+ export const SPEED_CORRIDOR_FACTOR = 1.2;
9
+ export const GPS_ACCURACY_FACTOR = 0.5;
10
+ export const TURN_LOOKAHEAD_DISTANCE_METERS = 40;
11
+ export const TURN_CORRIDOR_MULTIPLIER = 1.4;
12
+ // ── Snapping ──────────────────────────────────────────────────
13
+ export const MAX_SNAP_DISTANCE = 80;
14
+ export const BEARING_MISMATCH_THRESHOLD = 90;
15
+ export const BEARING_MISMATCH_PENALTY = 500;
16
+ export const INERTIA_PENALTY = 200;
17
+ export const INERTIA_SEGMENT_DIST = 5;
18
+ export const SNAP_WINDOW_SIZE = 25;
19
+ // ── Bearing ───────────────────────────────────────────────────
20
+ export const BEARING_SMOOTHING_ALPHA = 0.3;
21
+ export const BEARING_MIN_SPEED_MPS = 1.5;
22
+ export const BEARING_ALIGNMENT_THRESHOLD = 55;
23
+ // ── Dead Reckoning ────────────────────────────────────────────
24
+ export const DR_MAX_AGE_MS = 5000;
25
+ // ── Timing ────────────────────────────────────────────────────
26
+ export const OFF_ROUTE_CONFIRM_MS = 2000;
27
+ export const REJOIN_CONFIRM_MS = 1500;
28
+ export const REJOIN_COOLDOWN_MS = 12000;
29
+ export const STARTUP_GRACE_MS = 3000;
30
+ export const DETOUR_COMMIT_MS = 4000;
31
+ export const GPS_STALE_MS = 3000;
32
+ export const GPS_AGE_CAP_SECONDS = 10;
33
+ // ── Instruction ───────────────────────────────────────────────
34
+ export const MANEUVER_TRIGGER_DIST = 25;
35
+ // ── Predictor ─────────────────────────────────────────────────
36
+ export const BETA_CORRECTION = 0.15;
37
+ export const LOOK_AHEAD_SECONDS = 3;
38
+ export const MIN_LOOK_AHEAD_METERS = 20;
39
+ export const MAX_LOOK_AHEAD_METERS = 100;
40
+ // ── Arrival ───────────────────────────────────────────────────
41
+ export const ARRIVAL_THRESHOLD_METERS = 30;
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Module 4b: DeviationDetector
3
+ * Compound deviation detection using distance, bearing, and movement signals.
4
+ */
5
+ import { type Coordinate } from '../geo/haversine';
6
+ export interface DeviationSignals {
7
+ /** Distance from GPS to nearest route segment (metres) */
8
+ distanceToRoute: number;
9
+ /** Whether the distance exceeds the dynamic corridor */
10
+ outsideCorridor: boolean;
11
+ /** Whether the vehicle bearing diverges from the route bearing */
12
+ bearingMismatch: boolean;
13
+ /** Whether the vehicle is moving away from the next segment */
14
+ movingAway: boolean;
15
+ /** Compound confirmed: outsideCorridor AND (bearingMismatch OR movingAway) */
16
+ confirmed: boolean;
17
+ }
18
+ export interface ClosestSegment {
19
+ segmentIndex: number;
20
+ distance: number;
21
+ projection: Coordinate;
22
+ }
23
+ /**
24
+ * Find the closest segment to a GPS point within a search window.
25
+ */
26
+ export declare function findClosestSegment(gps: Coordinate, route: Coordinate[], startIdx: number, windowSize: number, routeFlowBearing?: number | null): ClosestSegment;
27
+ export declare class DeviationDetector {
28
+ /**
29
+ * Evaluate deviation signals for the current GPS position.
30
+ */
31
+ evaluate(gps: Coordinate, gpsBearing: number | null, speedMps: number, closest: ClosestSegment, route: Coordinate[], corridor: number, prevDistToNextSegment: number | null): DeviationSignals;
32
+ }
33
+ //# sourceMappingURL=DeviationDetector.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DeviationDetector.d.ts","sourceRoot":"","sources":["../../../../../core/src/corridor/DeviationDetector.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,KAAK,UAAU,EAAqB,MAAM,kBAAkB,CAAC;AAKtE,MAAM,WAAW,gBAAgB;IAC/B,0DAA0D;IAC1D,eAAe,EAAE,MAAM,CAAC;IACxB,wDAAwD;IACxD,eAAe,EAAE,OAAO,CAAC;IACzB,kEAAkE;IAClE,eAAe,EAAE,OAAO,CAAC;IACzB,+DAA+D;IAC/D,UAAU,EAAE,OAAO,CAAC;IACpB,8EAA8E;IAC9E,SAAS,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,cAAc;IAC7B,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,UAAU,CAAC;CACxB;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAChC,GAAG,EAAE,UAAU,EACf,KAAK,EAAE,UAAU,EAAE,EACnB,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,EAClB,gBAAgB,GAAE,MAAM,GAAG,IAAW,GACrC,cAAc,CA8ChB;AAED,qBAAa,iBAAiB;IAC5B;;OAEG;IACH,QAAQ,CACN,GAAG,EAAE,UAAU,EACf,UAAU,EAAE,MAAM,GAAG,IAAI,EACzB,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,cAAc,EACvB,KAAK,EAAE,UAAU,EAAE,EACnB,QAAQ,EAAE,MAAM,EAChB,qBAAqB,EAAE,MAAM,GAAG,IAAI,GACnC,gBAAgB;CAiCpB"}
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Module 4b: DeviationDetector
3
+ * Compound deviation detection using distance, bearing, and movement signals.
4
+ */
5
+ import { haversineDistance } from '../geo/haversine';
6
+ import { getBearingBetweenPoints, normalizeAngle } from '../geo/bearing';
7
+ import { BEARING_MISMATCH_THRESHOLD } from '../constants';
8
+ /**
9
+ * Find the closest segment to a GPS point within a search window.
10
+ */
11
+ export function findClosestSegment(gps, route, startIdx, windowSize, routeFlowBearing = null) {
12
+ let bestIdx = Math.max(0, startIdx);
13
+ let bestDist = Infinity;
14
+ let bestProj = route[bestIdx] ?? [0, 0];
15
+ const end = Math.min(startIdx + windowSize, route.length - 1);
16
+ for (let i = Math.max(0, startIdx); i < end; i++) {
17
+ const a = route[i];
18
+ const b = route[i + 1];
19
+ // Simple flat projection for speed (deviation detection doesn't need
20
+ // sub-metre accuracy — haversine is used for the final distance check)
21
+ const [px, py] = gps;
22
+ const [ax, ay] = a;
23
+ const [bx, by] = b;
24
+ const dx = bx - ax;
25
+ const dy = by - ay;
26
+ const lenSq = dx * dx + dy * dy;
27
+ let proj;
28
+ if (lenSq === 0) {
29
+ proj = a;
30
+ }
31
+ else {
32
+ let t = ((px - ax) * dx + (py - ay) * dy) / lenSq;
33
+ t = Math.max(0, Math.min(1, t));
34
+ proj = [ax + t * dx, ay + t * dy];
35
+ }
36
+ const d = haversineDistance(gps[1], gps[0], proj[1], proj[0]);
37
+ // Anti-parallel penalty: if route flow bearing is known, penalize
38
+ // segments going the opposite direction
39
+ if (routeFlowBearing !== null && d < bestDist * 1.5) {
40
+ const segBearing = getBearingBetweenPoints(a, b);
41
+ const diff = Math.abs(normalizeAngle(routeFlowBearing - segBearing));
42
+ if (diff > 120)
43
+ continue; // Very anti-parallel, skip
44
+ }
45
+ if (d < bestDist) {
46
+ bestDist = d;
47
+ bestIdx = i;
48
+ bestProj = proj;
49
+ }
50
+ }
51
+ return { segmentIndex: bestIdx, distance: bestDist, projection: bestProj };
52
+ }
53
+ export class DeviationDetector {
54
+ /**
55
+ * Evaluate deviation signals for the current GPS position.
56
+ */
57
+ evaluate(gps, gpsBearing, speedMps, closest, route, corridor, prevDistToNextSegment) {
58
+ const outsideCorridor = closest.distance > corridor;
59
+ // Bearing mismatch
60
+ let bearingMismatch = false;
61
+ if (gpsBearing !== null && closest.segmentIndex < route.length - 1) {
62
+ const segBearing = getBearingBetweenPoints(route[closest.segmentIndex], route[closest.segmentIndex + 1]);
63
+ const diff = Math.abs(normalizeAngle(gpsBearing - segBearing));
64
+ bearingMismatch = diff > BEARING_MISMATCH_THRESHOLD;
65
+ }
66
+ // Moving away detection
67
+ let movingAway = false;
68
+ if (prevDistToNextSegment !== null && speedMps > 1.5) {
69
+ const nextEnd = route[Math.min(closest.segmentIndex + 2, route.length - 1)];
70
+ const currentDistToNext = haversineDistance(gps[1], gps[0], nextEnd[1], nextEnd[0]);
71
+ movingAway = currentDistToNext > prevDistToNextSegment + 5;
72
+ }
73
+ // Compound confirmation: outside corridor AND (bearing mismatch OR moving away)
74
+ const confirmed = outsideCorridor && (bearingMismatch || movingAway);
75
+ return {
76
+ distanceToRoute: closest.distance,
77
+ outsideCorridor,
78
+ bearingMismatch,
79
+ movingAway,
80
+ confirmed,
81
+ };
82
+ }
83
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Module 4a: DynamicCorridor
3
+ * Speed-adaptive corridor width calculation.
4
+ */
5
+ /**
6
+ * Compute speed-adaptive corridor width in metres.
7
+ *
8
+ * Corridor widens with speed: BASE + speed * FACTOR, capped at MAX.
9
+ * GPS accuracy optionally widens the corridor further.
10
+ * Turn lookahead widens corridor when approaching upcoming maneuvers to prevent false off-route alerts.
11
+ */
12
+ export declare function calculateDynamicCorridor(speedMps: number, gpsAccuracy?: number | null, baseMeters?: number, maxMeters?: number, speedFactor?: number, accuracyFactor?: number, distanceToTurn?: number | null, turnMultiplier?: number, turnLookaheadDist?: number): number;
13
+ //# sourceMappingURL=DynamicCorridor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DynamicCorridor.d.ts","sourceRoot":"","sources":["../../../../../core/src/corridor/DynamicCorridor.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAWH;;;;;;GAMG;AACH,wBAAgB,wBAAwB,CACtC,QAAQ,EAAE,MAAM,EAChB,WAAW,GAAE,MAAM,GAAG,IAAW,EACjC,UAAU,GAAE,MAA6B,EACzC,SAAS,GAAE,MAA4B,EACvC,WAAW,GAAE,MAA8B,EAC3C,cAAc,GAAE,MAA4B,EAC5C,cAAc,GAAE,MAAM,GAAG,IAAW,EACpC,cAAc,GAAE,MAAiC,EACjD,iBAAiB,GAAE,MAAuC,GACzD,MAAM,CAgBR"}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Module 4a: DynamicCorridor
3
+ * Speed-adaptive corridor width calculation.
4
+ */
5
+ import { BASE_CORRIDOR_METERS, MAX_CORRIDOR_METERS, SPEED_CORRIDOR_FACTOR, GPS_ACCURACY_FACTOR, TURN_LOOKAHEAD_DISTANCE_METERS, TURN_CORRIDOR_MULTIPLIER, } from '../constants';
6
+ /**
7
+ * Compute speed-adaptive corridor width in metres.
8
+ *
9
+ * Corridor widens with speed: BASE + speed * FACTOR, capped at MAX.
10
+ * GPS accuracy optionally widens the corridor further.
11
+ * Turn lookahead widens corridor when approaching upcoming maneuvers to prevent false off-route alerts.
12
+ */
13
+ export function calculateDynamicCorridor(speedMps, gpsAccuracy = null, baseMeters = BASE_CORRIDOR_METERS, maxMeters = MAX_CORRIDOR_METERS, speedFactor = SPEED_CORRIDOR_FACTOR, accuracyFactor = GPS_ACCURACY_FACTOR, distanceToTurn = null, turnMultiplier = TURN_CORRIDOR_MULTIPLIER, turnLookaheadDist = TURN_LOOKAHEAD_DISTANCE_METERS) {
14
+ let corridor = baseMeters + speedMps * speedFactor;
15
+ if (gpsAccuracy !== null && gpsAccuracy > 0) {
16
+ corridor += gpsAccuracy * accuracyFactor;
17
+ }
18
+ let effectiveMax = maxMeters;
19
+ if (distanceToTurn !== null && distanceToTurn >= 0 && distanceToTurn <= turnLookaheadDist) {
20
+ const proximity = 1 - Math.max(0, distanceToTurn / turnLookaheadDist);
21
+ const scale = 1 + (turnMultiplier - 1) * proximity;
22
+ corridor *= scale;
23
+ effectiveMax = maxMeters * turnMultiplier;
24
+ }
25
+ return Math.min(corridor, effectiveMax);
26
+ }
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Module 3: DeadReckoning
3
+ * Predicts vehicle position during GPS outages or stale fixes.
4
+ */
5
+ import { type Coordinate } from '../geo/haversine';
6
+ export interface DRAnchor {
7
+ coord: Coordinate;
8
+ idx: number;
9
+ ts: number;
10
+ }
11
+ export interface DRPrediction {
12
+ predictedCoord: Coordinate;
13
+ isActive: boolean;
14
+ ageMs: number;
15
+ }
16
+ /**
17
+ * Given a snapped anchor on the route, extrapolate forward using speed and bearing.
18
+ */
19
+ export declare function computeDeadReckoningOnRoute(anchor: DRAnchor | null, speed: number, bearing: number, now: number, maxAgeMs?: number): DRPrediction;
20
+ /**
21
+ * Stateful dead reckoning engine.
22
+ */
23
+ export declare class DeadReckoningEngine {
24
+ private anchor;
25
+ private readonly maxAgeMs;
26
+ constructor(maxAgeMs?: number);
27
+ /** Set the anchor point (called when GPS is available and snapping succeeds) */
28
+ setAnchor(coord: Coordinate, idx: number, timestamp: number): void;
29
+ /** Predict position given current speed and bearing */
30
+ predict(speed: number, bearing: number, now: number): DRPrediction;
31
+ /** Clear the anchor (GPS restored) */
32
+ clearAnchor(): void;
33
+ getAnchor(): DRAnchor | null;
34
+ }
35
+ //# sourceMappingURL=DeadReckoning.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DeadReckoning.d.ts","sourceRoot":"","sources":["../../../../../core/src/dead-reckoning/DeadReckoning.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,KAAK,UAAU,EAAoB,MAAM,kBAAkB,CAAC;AAGrE,MAAM,WAAW,QAAQ;IACvB,KAAK,EAAE,UAAU,CAAC;IAClB,GAAG,EAAE,MAAM,CAAC;IACZ,EAAE,EAAE,MAAM,CAAC;CACZ;AAED,MAAM,WAAW,YAAY;IAC3B,cAAc,EAAE,UAAU,CAAC;IAC3B,QAAQ,EAAE,OAAO,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;GAEG;AACH,wBAAgB,2BAA2B,CACzC,MAAM,EAAE,QAAQ,GAAG,IAAI,EACvB,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,MAAM,EACf,GAAG,EAAE,MAAM,EACX,QAAQ,GAAE,MAAsB,GAC/B,YAAY,CAmBd;AAED;;GAEG;AACH,qBAAa,mBAAmB;IAC9B,OAAO,CAAC,MAAM,CAAyB;IACvC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;gBAEtB,QAAQ,GAAE,MAAsB;IAI5C,gFAAgF;IAChF,SAAS,CAAC,KAAK,EAAE,UAAU,EAAE,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI;IAIlE,uDAAuD;IACvD,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,YAAY;IAIlE,sCAAsC;IACtC,WAAW,IAAI,IAAI;IAInB,SAAS,IAAI,QAAQ,GAAG,IAAI;CAG7B"}
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Module 3: DeadReckoning
3
+ * Predicts vehicle position during GPS outages or stale fixes.
4
+ */
5
+ import { destinationPoint } from '../geo/haversine';
6
+ import { DR_MAX_AGE_MS } from '../constants';
7
+ /**
8
+ * Given a snapped anchor on the route, extrapolate forward using speed and bearing.
9
+ */
10
+ export function computeDeadReckoningOnRoute(anchor, speed, bearing, now, maxAgeMs = DR_MAX_AGE_MS) {
11
+ if (!anchor || speed < 0.5) {
12
+ return { predictedCoord: anchor?.coord ?? [0, 0], isActive: false, ageMs: 0 };
13
+ }
14
+ const ageMs = now - anchor.ts;
15
+ if (ageMs > maxAgeMs || ageMs < 0) {
16
+ return { predictedCoord: anchor.coord, isActive: false, ageMs };
17
+ }
18
+ const distM = speed * (ageMs / 1000);
19
+ const predicted = destinationPoint(anchor.coord[1], anchor.coord[0], bearing, distM);
20
+ return {
21
+ predictedCoord: predicted,
22
+ isActive: true,
23
+ ageMs,
24
+ };
25
+ }
26
+ /**
27
+ * Stateful dead reckoning engine.
28
+ */
29
+ export class DeadReckoningEngine {
30
+ constructor(maxAgeMs = DR_MAX_AGE_MS) {
31
+ this.anchor = null;
32
+ this.maxAgeMs = maxAgeMs;
33
+ }
34
+ /** Set the anchor point (called when GPS is available and snapping succeeds) */
35
+ setAnchor(coord, idx, timestamp) {
36
+ this.anchor = { coord, idx, ts: timestamp };
37
+ }
38
+ /** Predict position given current speed and bearing */
39
+ predict(speed, bearing, now) {
40
+ return computeDeadReckoningOnRoute(this.anchor, speed, bearing, now, this.maxAgeMs);
41
+ }
42
+ /** Clear the anchor (GPS restored) */
43
+ clearAnchor() {
44
+ this.anchor = null;
45
+ }
46
+ getAnchor() {
47
+ return this.anchor;
48
+ }
49
+ }
@@ -0,0 +1,70 @@
1
+ /**
2
+ * DirectionsProvider — Generic interface for route computation.
3
+ *
4
+ * All direction adapters (Mapbox, OSRM, Valhalla, ORS…) implement this contract.
5
+ * NavCore itself only depends on this interface — swap providers with zero engine changes.
6
+ */
7
+ import type { Coordinate } from '../geo/haversine';
8
+ export interface DirectionsOptions {
9
+ /** Travel mode */
10
+ profile?: 'driving' | 'cycling' | 'walking';
11
+ /** Include step-by-step maneuver data */
12
+ steps?: boolean;
13
+ /** Language for instruction text */
14
+ language?: string;
15
+ /** Return alternative routes */
16
+ alternatives?: boolean;
17
+ }
18
+ export interface DirectionsManeuver {
19
+ type: string;
20
+ modifier?: string;
21
+ location: Coordinate;
22
+ bearingBefore: number;
23
+ bearingAfter: number;
24
+ instruction?: string;
25
+ }
26
+ export interface DirectionsStep {
27
+ distance: number;
28
+ duration: number;
29
+ geometry: Coordinate[];
30
+ maneuver: DirectionsManeuver;
31
+ name?: string;
32
+ }
33
+ export interface DirectionsLeg {
34
+ distance: number;
35
+ duration: number;
36
+ steps?: DirectionsStep[];
37
+ }
38
+ export interface DirectionsResult {
39
+ /** Full route geometry as [lng, lat] coordinates */
40
+ geometry: Coordinate[];
41
+ /** Total distance in metres */
42
+ distance: number;
43
+ /** Total duration in seconds */
44
+ duration: number;
45
+ /** Per-leg breakdown */
46
+ legs: DirectionsLeg[];
47
+ /** Raw API response for provider-specific access */
48
+ raw?: unknown;
49
+ }
50
+ /**
51
+ * Any directions source implements this single method.
52
+ *
53
+ * @example
54
+ * ```typescript
55
+ * const provider: DirectionsProvider = new OSRMDirectionsProvider({ baseUrl: '...' });
56
+ * const result = await provider.getRoute([[lng1, lat1], [lng2, lat2]]);
57
+ * engine.setRoute(result.geometry);
58
+ * ```
59
+ */
60
+ export interface DirectionsProvider {
61
+ /**
62
+ * Compute a route through the given waypoints.
63
+ * Implementations MUST handle the case where `waypoints.length > provider limit`
64
+ * by chunking internally.
65
+ *
66
+ * @throws {Error} if fewer than 2 waypoints are given, or the API fails.
67
+ */
68
+ getRoute(waypoints: Coordinate[], options?: DirectionsOptions): Promise<DirectionsResult>;
69
+ }
70
+ //# sourceMappingURL=DirectionsProvider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DirectionsProvider.d.ts","sourceRoot":"","sources":["../../../../../core/src/directions/DirectionsProvider.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAInD,MAAM,WAAW,iBAAiB;IAChC,kBAAkB;IAClB,OAAO,CAAC,EAAE,SAAS,GAAG,SAAS,GAAG,SAAS,CAAC;IAC5C,yCAAyC;IACzC,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,oCAAoC;IACpC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,gCAAgC;IAChC,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AAID,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,UAAU,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,UAAU,EAAE,CAAC;IACvB,QAAQ,EAAE,kBAAkB,CAAC;IAC7B,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,cAAc,EAAE,CAAC;CAC1B;AAED,MAAM,WAAW,gBAAgB;IAC/B,oDAAoD;IACpD,QAAQ,EAAE,UAAU,EAAE,CAAC;IACvB,+BAA+B;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,gCAAgC;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,wBAAwB;IACxB,IAAI,EAAE,aAAa,EAAE,CAAC;IACtB,oDAAoD;IACpD,GAAG,CAAC,EAAE,OAAO,CAAC;CACf;AAID;;;;;;;;;GASG;AACH,MAAM,WAAW,kBAAkB;IACjC;;;;;;OAMG;IACH,QAAQ,CAAC,SAAS,EAAE,UAAU,EAAE,EAAE,OAAO,CAAC,EAAE,iBAAiB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAAC;CAC3F"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * DirectionsProvider — Generic interface for route computation.
3
+ *
4
+ * All direction adapters (Mapbox, OSRM, Valhalla, ORS…) implement this contract.
5
+ * NavCore itself only depends on this interface — swap providers with zero engine changes.
6
+ */
7
+ export {};
@@ -0,0 +1,26 @@
1
+ /**
2
+ * OSRMDirectionsProvider
3
+ *
4
+ * Self-hosted OSRM (Open Source Routing Machine) adapter.
5
+ * Zero API key required. Ideal for self-hosted deployments.
6
+ *
7
+ * @see https://project-osrm.org/docs/v5.24.0/api/
8
+ */
9
+ import type { Coordinate } from '../geo/haversine';
10
+ import type { DirectionsProvider, DirectionsOptions, DirectionsResult } from './DirectionsProvider';
11
+ export interface OSRMConfig {
12
+ /** Base URL of your OSRM server, e.g. 'http://router.project-osrm.org' */
13
+ baseUrl: string;
14
+ /** Routing profile (default: 'driving') */
15
+ profile?: 'driving' | 'cycling' | 'walking';
16
+ /** Max waypoints per API request (OSRM supports unlimited, but chunking keeps requests fast) */
17
+ maxWaypointsPerChunk?: number;
18
+ }
19
+ export declare class OSRMDirectionsProvider implements DirectionsProvider {
20
+ private readonly config;
21
+ constructor(config: OSRMConfig);
22
+ getRoute(waypoints: Coordinate[], options?: DirectionsOptions): Promise<DirectionsResult>;
23
+ private fetchRoute;
24
+ private fetchChunked;
25
+ }
26
+ //# sourceMappingURL=OSRMDirectionsProvider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"OSRMDirectionsProvider.d.ts","sourceRoot":"","sources":["../../../../../core/src/directions/OSRMDirectionsProvider.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,KAAK,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAEpG,MAAM,WAAW,UAAU;IACzB,0EAA0E;IAC1E,OAAO,EAAE,MAAM,CAAC;IAChB,2CAA2C;IAC3C,OAAO,CAAC,EAAE,SAAS,GAAG,SAAS,GAAG,SAAS,CAAC;IAC5C,gGAAgG;IAChG,oBAAoB,CAAC,EAAE,MAAM,CAAC;CAC/B;AAID,qBAAa,sBAAuB,YAAW,kBAAkB;IAC/D,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAuB;gBAElC,MAAM,EAAE,UAAU;IAQxB,QAAQ,CAAC,SAAS,EAAE,UAAU,EAAE,EAAE,OAAO,CAAC,EAAE,iBAAiB,GAAG,OAAO,CAAC,gBAAgB,CAAC;YAYjF,UAAU;YA8CV,YAAY;CA+B3B"}
@@ -0,0 +1,90 @@
1
+ /**
2
+ * OSRMDirectionsProvider
3
+ *
4
+ * Self-hosted OSRM (Open Source Routing Machine) adapter.
5
+ * Zero API key required. Ideal for self-hosted deployments.
6
+ *
7
+ * @see https://project-osrm.org/docs/v5.24.0/api/
8
+ */
9
+ const DEFAULT_MAX_CHUNK = 100;
10
+ export class OSRMDirectionsProvider {
11
+ constructor(config) {
12
+ this.config = {
13
+ baseUrl: config.baseUrl.replace(/\/$/, ''),
14
+ profile: config.profile ?? 'driving',
15
+ maxWaypointsPerChunk: config.maxWaypointsPerChunk ?? DEFAULT_MAX_CHUNK,
16
+ };
17
+ }
18
+ async getRoute(waypoints, options) {
19
+ if (waypoints.length < 2)
20
+ throw new Error('[OSRM] At least 2 waypoints required');
21
+ const profile = options?.profile ?? this.config.profile;
22
+ if (waypoints.length <= this.config.maxWaypointsPerChunk) {
23
+ return this.fetchRoute(waypoints, profile, options);
24
+ }
25
+ return this.fetchChunked(waypoints, profile, options);
26
+ }
27
+ async fetchRoute(waypoints, profile, options) {
28
+ const coords = waypoints.map(w => `${w[0]},${w[1]}`).join(';');
29
+ const steps = options?.steps !== false ? 'true' : 'false';
30
+ const url = `${this.config.baseUrl}/route/v1/${profile}/${coords}` +
31
+ `?overview=full&geometries=geojson&steps=${steps}&annotations=false`;
32
+ const res = await fetch(url);
33
+ if (!res.ok)
34
+ throw new Error(`[OSRM] HTTP ${res.status}: ${res.statusText}`);
35
+ const data = await res.json();
36
+ if (data.code !== 'Ok' || !data.routes?.length) {
37
+ throw new Error(`[OSRM] No route found: ${data.code}`);
38
+ }
39
+ const route = data.routes[0];
40
+ return {
41
+ geometry: route.geometry.coordinates,
42
+ distance: route.distance,
43
+ duration: route.duration,
44
+ legs: (route.legs ?? []).map((leg) => ({
45
+ distance: leg.distance,
46
+ duration: leg.duration,
47
+ steps: (leg.steps ?? []).map((step) => ({
48
+ distance: step.distance,
49
+ duration: step.duration,
50
+ geometry: step.geometry?.coordinates ?? [],
51
+ maneuver: {
52
+ type: step.maneuver?.type ?? 'unknown',
53
+ modifier: step.maneuver?.modifier,
54
+ location: step.maneuver?.location ?? [0, 0],
55
+ bearingBefore: step.maneuver?.bearing_before ?? 0,
56
+ bearingAfter: step.maneuver?.bearing_after ?? 0,
57
+ instruction: step.name ?? undefined,
58
+ },
59
+ name: step.name,
60
+ })),
61
+ })),
62
+ raw: data,
63
+ };
64
+ }
65
+ async fetchChunked(waypoints, profile, options) {
66
+ const size = this.config.maxWaypointsPerChunk;
67
+ const chunks = [];
68
+ for (let i = 0; i < waypoints.length; i += size - 1) {
69
+ const chunk = waypoints.slice(i, i + size);
70
+ if (chunk.length < 2)
71
+ break;
72
+ chunks.push(chunk);
73
+ if (i + size >= waypoints.length)
74
+ break;
75
+ }
76
+ const results = await Promise.all(chunks.map(c => this.fetchRoute(c, profile, options)));
77
+ const geometry = [];
78
+ let distance = 0;
79
+ let duration = 0;
80
+ const legs = [];
81
+ for (let i = 0; i < results.length; i++) {
82
+ const r = results[i];
83
+ geometry.push(...(i === 0 ? r.geometry : r.geometry.slice(1)));
84
+ distance += r.distance;
85
+ duration += r.duration;
86
+ legs.push(...r.legs);
87
+ }
88
+ return { geometry, distance, duration, legs, raw: results.map(r => r.raw) };
89
+ }
90
+ }
@@ -0,0 +1,30 @@
1
+ /**
2
+ * OpenRouteServiceProvider
3
+ *
4
+ * OpenRouteService (ORS) API adapter. Free tier: 2000 req/day.
5
+ * Requires an API key from https://openrouteservice.org/
6
+ *
7
+ * @see https://openrouteservice.org/dev/#/api-docs/v2/directions
8
+ */
9
+ import type { Coordinate } from '../geo/haversine';
10
+ import type { DirectionsProvider, DirectionsOptions, DirectionsResult } from './DirectionsProvider';
11
+ export type ORSProfile = 'driving-car' | 'driving-hgv' | 'cycling-regular' | 'cycling-road' | 'cycling-mountain' | 'foot-walking' | 'foot-hiking' | 'wheelchair';
12
+ export interface ORSConfig {
13
+ /** ORS API key */
14
+ apiKey: string;
15
+ /** Routing profile (default: 'driving-car') */
16
+ profile?: ORSProfile;
17
+ /** API base URL (default: ORS public API) */
18
+ baseUrl?: string;
19
+ /** Max waypoints per request (ORS limit: 50) */
20
+ maxWaypointsPerChunk?: number;
21
+ }
22
+ export declare class OpenRouteServiceProvider implements DirectionsProvider {
23
+ private readonly config;
24
+ constructor(config: ORSConfig);
25
+ getRoute(waypoints: Coordinate[], options?: DirectionsOptions): Promise<DirectionsResult>;
26
+ private profileFromOptions;
27
+ private fetchRoute;
28
+ private fetchChunked;
29
+ }
30
+ //# sourceMappingURL=OpenRouteServiceProvider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"OpenRouteServiceProvider.d.ts","sourceRoot":"","sources":["../../../../../core/src/directions/OpenRouteServiceProvider.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,KAAK,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAEpG,MAAM,MAAM,UAAU,GAClB,aAAa,GACb,aAAa,GACb,iBAAiB,GACjB,cAAc,GACd,kBAAkB,GAClB,cAAc,GACd,aAAa,GACb,YAAY,CAAC;AAEjB,MAAM,WAAW,SAAS;IACxB,kBAAkB;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,+CAA+C;IAC/C,OAAO,CAAC,EAAE,UAAU,CAAC;IACrB,6CAA6C;IAC7C,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,gDAAgD;IAChD,oBAAoB,CAAC,EAAE,MAAM,CAAC;CAC/B;AAKD,qBAAa,wBAAyB,YAAW,kBAAkB;IACjE,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAsB;gBAEjC,MAAM,EAAE,SAAS;IASvB,QAAQ,CAAC,SAAS,EAAE,UAAU,EAAE,EAAE,OAAO,CAAC,EAAE,iBAAiB,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAY/F,OAAO,CAAC,kBAAkB;YAQZ,UAAU;YA4DV,YAAY;CA+B3B"}
@@ -0,0 +1,111 @@
1
+ /**
2
+ * OpenRouteServiceProvider
3
+ *
4
+ * OpenRouteService (ORS) API adapter. Free tier: 2000 req/day.
5
+ * Requires an API key from https://openrouteservice.org/
6
+ *
7
+ * @see https://openrouteservice.org/dev/#/api-docs/v2/directions
8
+ */
9
+ const DEFAULT_BASE_URL = 'https://api.openrouteservice.org/v2/directions';
10
+ const DEFAULT_MAX_CHUNK = 50;
11
+ export class OpenRouteServiceProvider {
12
+ constructor(config) {
13
+ this.config = {
14
+ apiKey: config.apiKey,
15
+ profile: config.profile ?? 'driving-car',
16
+ baseUrl: config.baseUrl ?? DEFAULT_BASE_URL,
17
+ maxWaypointsPerChunk: config.maxWaypointsPerChunk ?? DEFAULT_MAX_CHUNK,
18
+ };
19
+ }
20
+ async getRoute(waypoints, options) {
21
+ if (waypoints.length < 2)
22
+ throw new Error('[ORS] At least 2 waypoints required');
23
+ const profile = this.profileFromOptions(options);
24
+ if (waypoints.length <= this.config.maxWaypointsPerChunk) {
25
+ return this.fetchRoute(waypoints, profile, options);
26
+ }
27
+ return this.fetchChunked(waypoints, profile, options);
28
+ }
29
+ profileFromOptions(options) {
30
+ switch (options?.profile) {
31
+ case 'cycling': return 'cycling-regular';
32
+ case 'walking': return 'foot-walking';
33
+ default: return this.config.profile;
34
+ }
35
+ }
36
+ async fetchRoute(waypoints, profile, options) {
37
+ const body = JSON.stringify({
38
+ coordinates: waypoints, // ORS uses [lng, lat] natively
39
+ instructions: options?.steps !== false,
40
+ language: options?.language ?? 'en',
41
+ });
42
+ const res = await fetch(`${this.config.baseUrl}/${profile}/geojson`, {
43
+ method: 'POST',
44
+ headers: {
45
+ 'Content-Type': 'application/json',
46
+ 'Authorization': this.config.apiKey,
47
+ },
48
+ body,
49
+ });
50
+ if (!res.ok) {
51
+ const errText = await res.text();
52
+ throw new Error(`[ORS] HTTP ${res.status}: ${errText}`);
53
+ }
54
+ const data = await res.json();
55
+ const feature = data.features?.[0];
56
+ if (!feature)
57
+ throw new Error('[ORS] No route returned');
58
+ const summary = feature.properties?.summary ?? {};
59
+ const segments = feature.properties?.segments ?? [];
60
+ const legs = segments.map((seg) => ({
61
+ distance: seg.distance ?? 0,
62
+ duration: seg.duration ?? 0,
63
+ steps: (seg.steps ?? []).map((step) => ({
64
+ distance: step.distance ?? 0,
65
+ duration: step.duration ?? 0,
66
+ geometry: [],
67
+ maneuver: {
68
+ type: step.type?.toString() ?? 'unknown',
69
+ modifier: undefined,
70
+ location: waypoints[0],
71
+ bearingBefore: 0,
72
+ bearingAfter: 0,
73
+ instruction: step.instruction,
74
+ },
75
+ name: step.name,
76
+ })),
77
+ }));
78
+ return {
79
+ geometry: (feature.geometry?.coordinates ?? []),
80
+ distance: summary.distance ?? 0,
81
+ duration: summary.duration ?? 0,
82
+ legs,
83
+ raw: data,
84
+ };
85
+ }
86
+ async fetchChunked(waypoints, profile, options) {
87
+ const size = this.config.maxWaypointsPerChunk;
88
+ const chunks = [];
89
+ for (let i = 0; i < waypoints.length; i += size - 1) {
90
+ const chunk = waypoints.slice(i, i + size);
91
+ if (chunk.length < 2)
92
+ break;
93
+ chunks.push(chunk);
94
+ if (i + size >= waypoints.length)
95
+ break;
96
+ }
97
+ const results = await Promise.all(chunks.map(c => this.fetchRoute(c, profile, options)));
98
+ const geometry = [];
99
+ let distance = 0;
100
+ let duration = 0;
101
+ const legs = [];
102
+ for (let i = 0; i < results.length; i++) {
103
+ const r = results[i];
104
+ geometry.push(...(i === 0 ? r.geometry : r.geometry.slice(1)));
105
+ distance += r.distance;
106
+ duration += r.duration;
107
+ legs.push(...r.legs);
108
+ }
109
+ return { geometry, distance, duration, legs, raw: results.map(r => r.raw) };
110
+ }
111
+ }