@ingissa/navcore-react-native 1.0.3 → 1.0.7
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.
- package/LICENSE +3 -0
- package/dist/core/src/NavCore.d.ts +142 -0
- package/dist/core/src/NavCore.d.ts.map +1 -0
- package/dist/core/src/NavCore.js +390 -0
- package/dist/core/src/bearing/BearingEngine.d.ts +24 -0
- package/dist/core/src/bearing/BearingEngine.d.ts.map +1 -0
- package/dist/core/src/bearing/BearingEngine.js +50 -0
- package/dist/core/src/constants.d.ts +34 -0
- package/dist/core/src/constants.d.ts.map +1 -0
- package/dist/core/src/constants.js +41 -0
- package/dist/core/src/corridor/DeviationDetector.d.ts +33 -0
- package/dist/core/src/corridor/DeviationDetector.d.ts.map +1 -0
- package/dist/core/src/corridor/DeviationDetector.js +83 -0
- package/dist/core/src/corridor/DynamicCorridor.d.ts +13 -0
- package/dist/core/src/corridor/DynamicCorridor.d.ts.map +1 -0
- package/dist/core/src/corridor/DynamicCorridor.js +26 -0
- package/dist/core/src/dead-reckoning/DeadReckoning.d.ts +35 -0
- package/dist/core/src/dead-reckoning/DeadReckoning.d.ts.map +1 -0
- package/dist/core/src/dead-reckoning/DeadReckoning.js +49 -0
- package/dist/core/src/directions/DirectionsProvider.d.ts +70 -0
- package/dist/core/src/directions/DirectionsProvider.d.ts.map +1 -0
- package/dist/core/src/directions/DirectionsProvider.js +7 -0
- package/dist/core/src/directions/OSRMDirectionsProvider.d.ts +26 -0
- package/dist/core/src/directions/OSRMDirectionsProvider.d.ts.map +1 -0
- package/dist/core/src/directions/OSRMDirectionsProvider.js +90 -0
- package/dist/core/src/directions/OpenRouteServiceProvider.d.ts +30 -0
- package/dist/core/src/directions/OpenRouteServiceProvider.d.ts.map +1 -0
- package/dist/core/src/directions/OpenRouteServiceProvider.js +111 -0
- package/dist/core/src/directions/ValhallaDirectionsProvider.d.ts +33 -0
- package/dist/core/src/directions/ValhallaDirectionsProvider.d.ts.map +1 -0
- package/dist/core/src/directions/ValhallaDirectionsProvider.js +155 -0
- package/dist/core/src/eta/ETAEngine.d.ts +61 -0
- package/dist/core/src/eta/ETAEngine.d.ts.map +1 -0
- package/dist/core/src/eta/ETAEngine.js +74 -0
- package/dist/core/src/features.d.ts +76 -0
- package/dist/core/src/features.d.ts.map +1 -0
- package/dist/core/src/features.js +76 -0
- package/dist/core/src/geo/bearing.d.ts +11 -0
- package/dist/core/src/geo/bearing.d.ts.map +1 -0
- package/dist/core/src/geo/bearing.js +25 -0
- package/dist/core/src/geo/cumulative.d.ts +9 -0
- package/dist/core/src/geo/cumulative.d.ts.map +1 -0
- package/dist/core/src/geo/cumulative.js +20 -0
- package/dist/core/src/geo/haversine.d.ts +13 -0
- package/dist/core/src/geo/haversine.d.ts.map +1 -0
- package/dist/core/src/geo/haversine.js +39 -0
- package/dist/core/src/geo/projection.d.ts +7 -0
- package/dist/core/src/geo/projection.d.ts.map +1 -0
- package/dist/core/src/geo/projection.js +17 -0
- package/dist/core/src/geofencing/GeofencingEngine.d.ts +93 -0
- package/dist/core/src/geofencing/GeofencingEngine.d.ts.map +1 -0
- package/dist/core/src/geofencing/GeofencingEngine.js +154 -0
- package/dist/core/src/index.d.ts +66 -0
- package/dist/core/src/index.d.ts.map +1 -0
- package/dist/core/src/index.js +62 -0
- package/dist/core/src/kalman/KalmanFilter2D.d.ts +46 -0
- package/dist/core/src/kalman/KalmanFilter2D.d.ts.map +1 -0
- package/dist/core/src/kalman/KalmanFilter2D.js +77 -0
- package/dist/core/src/license/LicenseManager.d.ts +40 -0
- package/dist/core/src/license/LicenseManager.d.ts.map +1 -0
- package/dist/core/src/license/LicenseManager.js +119 -0
- package/dist/core/src/license/verifyP256Signature.d.ts +2 -0
- package/dist/core/src/license/verifyP256Signature.d.ts.map +1 -0
- package/dist/core/src/license/verifyP256Signature.js +26 -0
- package/dist/core/src/off-route/NavigationFSM.d.ts +42 -0
- package/dist/core/src/off-route/NavigationFSM.d.ts.map +1 -0
- package/dist/core/src/off-route/NavigationFSM.js +56 -0
- package/dist/core/src/off-route/OffRouteFSM.d.ts +70 -0
- package/dist/core/src/off-route/OffRouteFSM.d.ts.map +1 -0
- package/dist/core/src/off-route/OffRouteFSM.js +151 -0
- package/dist/core/src/predictor/RouteOffsetPredictor.d.ts +24 -0
- package/dist/core/src/predictor/RouteOffsetPredictor.d.ts.map +1 -0
- package/dist/core/src/predictor/RouteOffsetPredictor.js +55 -0
- package/dist/core/src/progress/InstructionEditor.d.ts +77 -0
- package/dist/core/src/progress/InstructionEditor.d.ts.map +1 -0
- package/dist/core/src/progress/InstructionEditor.js +151 -0
- package/dist/core/src/progress/InstructionResolver.d.ts +25 -0
- package/dist/core/src/progress/InstructionResolver.d.ts.map +1 -0
- package/dist/core/src/progress/InstructionResolver.js +78 -0
- package/dist/core/src/progress/ProgressTracker.d.ts +12 -0
- package/dist/core/src/progress/ProgressTracker.d.ts.map +1 -0
- package/dist/core/src/progress/ProgressTracker.js +22 -0
- package/dist/core/src/renderer/MapRendererAdapter.d.ts +72 -0
- package/dist/core/src/renderer/MapRendererAdapter.d.ts.map +1 -0
- package/dist/core/src/renderer/MapRendererAdapter.js +19 -0
- package/dist/core/src/route-builder/CustomRouteBuilder.d.ts +83 -0
- package/dist/core/src/route-builder/CustomRouteBuilder.d.ts.map +1 -0
- package/dist/core/src/route-builder/CustomRouteBuilder.js +160 -0
- package/dist/core/src/route-plan/RoutePlan.d.ts +38 -0
- package/dist/core/src/route-plan/RoutePlan.d.ts.map +1 -0
- package/dist/core/src/route-plan/RoutePlan.js +102 -0
- package/dist/core/src/session/NavigationSession.d.ts +81 -0
- package/dist/core/src/session/NavigationSession.d.ts.map +1 -0
- package/dist/core/src/session/NavigationSession.js +115 -0
- package/dist/core/src/snapper/ParallelRoadResolver.d.ts +86 -0
- package/dist/core/src/snapper/ParallelRoadResolver.d.ts.map +1 -0
- package/dist/core/src/snapper/ParallelRoadResolver.js +83 -0
- package/dist/core/src/snapper/RouteSnapper.d.ts +38 -0
- package/dist/core/src/snapper/RouteSnapper.d.ts.map +1 -0
- package/dist/core/src/snapper/RouteSnapper.js +80 -0
- package/dist/core/src/voice/VoiceTriggerEngine.d.ts +80 -0
- package/dist/core/src/voice/VoiceTriggerEngine.d.ts.map +1 -0
- package/dist/core/src/voice/VoiceTriggerEngine.js +108 -0
- package/dist/react-native/src/NavCoreProvider.d.ts.map +1 -0
- package/dist/react-native/src/index.d.ts +10 -0
- package/dist/react-native/src/index.d.ts.map +1 -0
- package/dist/{index.js → react-native/src/index.js} +1 -0
- package/dist/{useNavCore.d.ts → react-native/src/useNavCore.d.ts} +7 -5
- package/dist/react-native/src/useNavCore.d.ts.map +1 -0
- package/dist/{useNavCore.js → react-native/src/useNavCore.js} +21 -47
- package/dist/react-native/src/useNavSelectors.d.ts +42 -0
- package/dist/react-native/src/useNavSelectors.d.ts.map +1 -0
- package/dist/react-native/src/useNavSelectors.js +103 -0
- package/package.json +17 -9
- package/src/NavCoreProvider.tsx +59 -0
- package/{dist/index.d.ts → src/index.ts} +18 -9
- package/src/useNavCore.ts +208 -0
- package/src/useNavSelectors.ts +143 -0
- package/dist/NavCoreProvider.d.ts.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/useNavCore.d.ts.map +0 -1
- /package/dist/{NavCoreProvider.d.ts → react-native/src/NavCoreProvider.d.ts} +0 -0
- /package/dist/{NavCoreProvider.js → react-native/src/NavCoreProvider.js} +0 -0
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* InstructionEditor — Fluent builder for NavInstruction arrays.
|
|
3
|
+
*
|
|
4
|
+
* Create, edit, and annotate navigation instructions for custom routes.
|
|
5
|
+
* Supports standard turn instructions and exam-specific annotations.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```typescript
|
|
9
|
+
* const editor = new InstructionEditor();
|
|
10
|
+
* editor
|
|
11
|
+
* .addTurn([2.347, 48.859], 'Turn left onto Rue de Rivoli', 42)
|
|
12
|
+
* .addExamPoint([2.350, 48.860], 'Check mirrors before changing lane', 50)
|
|
13
|
+
* .addCheckpoint([2.355, 48.861], 65);
|
|
14
|
+
*
|
|
15
|
+
* editor.attachToCumulative(routeGeometry);
|
|
16
|
+
* engine.setRoute(routeGeometry, editor.toArray());
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
import { haversineDistance } from '../geo/haversine';
|
|
20
|
+
import { getBearingBetweenPoints } from '../geo/bearing';
|
|
21
|
+
let _idCounter = 0;
|
|
22
|
+
function genId() {
|
|
23
|
+
return `instr_${Date.now()}_${++_idCounter}`;
|
|
24
|
+
}
|
|
25
|
+
export class InstructionEditor {
|
|
26
|
+
constructor() {
|
|
27
|
+
this.instructions = [];
|
|
28
|
+
}
|
|
29
|
+
// ── Fluent add methods ──────────────────────────────────────────────────────
|
|
30
|
+
/** Add a turn instruction at the given route geometry index */
|
|
31
|
+
addTurn(location, text, geometryIndex, opts) {
|
|
32
|
+
this.instructions.push({
|
|
33
|
+
id: genId(), type: 'turn',
|
|
34
|
+
location, text, instruction: text, geometryIndex,
|
|
35
|
+
silent: opts?.silent, meta: opts?.meta,
|
|
36
|
+
});
|
|
37
|
+
return this;
|
|
38
|
+
}
|
|
39
|
+
/** Add an exam-specific point (e.g. "Check mirrors", "Yield here") */
|
|
40
|
+
addExamPoint(location, text, geometryIndex, opts) {
|
|
41
|
+
this.instructions.push({
|
|
42
|
+
id: genId(), type: 'exam_point',
|
|
43
|
+
location, text, instruction: text, geometryIndex,
|
|
44
|
+
severity: opts?.severity ?? 'info',
|
|
45
|
+
silent: opts?.silent,
|
|
46
|
+
meta: opts?.meta,
|
|
47
|
+
});
|
|
48
|
+
return this;
|
|
49
|
+
}
|
|
50
|
+
/** Add a silent progress checkpoint (does not trigger voice) */
|
|
51
|
+
addCheckpoint(location, geometryIndex, meta) {
|
|
52
|
+
this.instructions.push({
|
|
53
|
+
id: genId(), type: 'checkpoint',
|
|
54
|
+
location, text: '', instruction: '', geometryIndex,
|
|
55
|
+
silent: true, meta,
|
|
56
|
+
});
|
|
57
|
+
return this;
|
|
58
|
+
}
|
|
59
|
+
/** Add a departure instruction (auto-inserted at index 0) */
|
|
60
|
+
addDepart(location, text = 'Start navigation') {
|
|
61
|
+
const instr = {
|
|
62
|
+
id: genId(), type: 'depart',
|
|
63
|
+
location, text, instruction: text, geometryIndex: 0,
|
|
64
|
+
};
|
|
65
|
+
this.instructions.unshift(instr);
|
|
66
|
+
return this;
|
|
67
|
+
}
|
|
68
|
+
/** Add an arrival instruction */
|
|
69
|
+
addArrive(location, text = 'You have arrived') {
|
|
70
|
+
this.instructions.push({
|
|
71
|
+
id: genId(), type: 'arrive',
|
|
72
|
+
location, text, instruction: text,
|
|
73
|
+
geometryIndex: Number.MAX_SAFE_INTEGER,
|
|
74
|
+
});
|
|
75
|
+
return this;
|
|
76
|
+
}
|
|
77
|
+
// ── Edit operations ─────────────────────────────────────────────────────────
|
|
78
|
+
/** Update any field of an instruction by id */
|
|
79
|
+
update(id, patch) {
|
|
80
|
+
const idx = this.instructions.findIndex(i => i.id === id);
|
|
81
|
+
if (idx === -1)
|
|
82
|
+
throw new Error(`[InstructionEditor] Instruction ${id} not found`);
|
|
83
|
+
this.instructions[idx] = { ...this.instructions[idx], ...patch };
|
|
84
|
+
return this;
|
|
85
|
+
}
|
|
86
|
+
/** Remove an instruction by id */
|
|
87
|
+
remove(id) {
|
|
88
|
+
const idx = this.instructions.findIndex(i => i.id === id);
|
|
89
|
+
if (idx === -1)
|
|
90
|
+
throw new Error(`[InstructionEditor] Instruction ${id} not found`);
|
|
91
|
+
this.instructions.splice(idx, 1);
|
|
92
|
+
return this;
|
|
93
|
+
}
|
|
94
|
+
/** Move an instruction to a new position in the list */
|
|
95
|
+
reorder(id, newIndex) {
|
|
96
|
+
const idx = this.instructions.findIndex(i => i.id === id);
|
|
97
|
+
if (idx === -1)
|
|
98
|
+
throw new Error(`[InstructionEditor] Instruction ${id} not found`);
|
|
99
|
+
const [item] = this.instructions.splice(idx, 1);
|
|
100
|
+
this.instructions.splice(Math.max(0, newIndex), 0, item);
|
|
101
|
+
return this;
|
|
102
|
+
}
|
|
103
|
+
/** Sort instructions by geometryIndex ascending */
|
|
104
|
+
sort() {
|
|
105
|
+
this.instructions.sort((a, b) => a.geometryIndex - b.geometryIndex);
|
|
106
|
+
return this;
|
|
107
|
+
}
|
|
108
|
+
/** Remove all instructions */
|
|
109
|
+
clear() {
|
|
110
|
+
this.instructions = [];
|
|
111
|
+
return this;
|
|
112
|
+
}
|
|
113
|
+
// ── Geometry attachment ─────────────────────────────────────────────────────
|
|
114
|
+
/**
|
|
115
|
+
* Compute and attach `cumulativeOffset` and `bearingBefore`/`bearingAfter`
|
|
116
|
+
* from the route geometry. Call this after loading the route but before
|
|
117
|
+
* passing instructions to the engine.
|
|
118
|
+
*/
|
|
119
|
+
attachToCumulative(route) {
|
|
120
|
+
if (route.length < 2)
|
|
121
|
+
return this;
|
|
122
|
+
// Build cumulative distance array
|
|
123
|
+
const cum = [0];
|
|
124
|
+
for (let i = 1; i < route.length; i++) {
|
|
125
|
+
const prev = route[i - 1];
|
|
126
|
+
const curr = route[i];
|
|
127
|
+
cum.push(cum[i - 1] + haversineDistance(prev[1], prev[0], curr[1], curr[0]));
|
|
128
|
+
}
|
|
129
|
+
for (const instr of this.instructions) {
|
|
130
|
+
const idx = Math.min(instr.geometryIndex, route.length - 1);
|
|
131
|
+
instr.cumulativeOffset = cum[idx] ?? 0;
|
|
132
|
+
if (idx > 0 && idx < route.length - 1) {
|
|
133
|
+
instr.bearingBefore = getBearingBetweenPoints(route[idx - 1], route[idx]);
|
|
134
|
+
instr.bearingAfter = getBearingBetweenPoints(route[idx], route[idx + 1]);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return this;
|
|
138
|
+
}
|
|
139
|
+
// ── Import / Export ─────────────────────────────────────────────────────────
|
|
140
|
+
/** Export as plain NavInstruction[] for use with engine.setRoute() */
|
|
141
|
+
toArray() {
|
|
142
|
+
return this.instructions.map(({ id: _id, type: _type, severity: _s, meta: _m, ...rest }) => rest);
|
|
143
|
+
}
|
|
144
|
+
/** Export rich instructions with all metadata */
|
|
145
|
+
toRichArray() {
|
|
146
|
+
return [...this.instructions];
|
|
147
|
+
}
|
|
148
|
+
get size() {
|
|
149
|
+
return this.instructions.length;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Module 7: InstructionResolver
|
|
3
|
+
* Resolves the next upcoming instruction and determines if it should be triggered.
|
|
4
|
+
*/
|
|
5
|
+
import { type Coordinate } from '../geo/haversine';
|
|
6
|
+
/** Navigation instruction (mirrors @navcore/types NavInstruction) */
|
|
7
|
+
export interface NavInstruction {
|
|
8
|
+
location: Coordinate;
|
|
9
|
+
geometryIndex: number;
|
|
10
|
+
text?: string;
|
|
11
|
+
instruction?: string;
|
|
12
|
+
cumulativeOffset?: number;
|
|
13
|
+
bearingBefore?: number;
|
|
14
|
+
bearingAfter?: number;
|
|
15
|
+
}
|
|
16
|
+
export interface InstructionResult {
|
|
17
|
+
nextIdx: number;
|
|
18
|
+
instruction: NavInstruction | null;
|
|
19
|
+
wasTriggered: boolean;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Resolve which instruction is next and whether it should trigger now.
|
|
23
|
+
*/
|
|
24
|
+
export declare function resolveNextInstruction(currentGeometryIdx: number, instructions: NavInstruction[], currentInstrIdx: number, route: Coordinate[], currentCoord: Coordinate, vehicleBearing: number | null, vehicleDistance: number, speed?: number, isCircuit?: boolean): InstructionResult;
|
|
25
|
+
//# sourceMappingURL=InstructionResolver.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"InstructionResolver.d.ts","sourceRoot":"","sources":["../../../../../core/src/progress/InstructionResolver.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,KAAK,UAAU,EAAqB,MAAM,kBAAkB,CAAC;AAItE,qEAAqE;AACrE,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,UAAU,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAOD,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,cAAc,GAAG,IAAI,CAAC;IACnC,YAAY,EAAE,OAAO,CAAC;CACvB;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CACpC,kBAAkB,EAAE,MAAM,EAC1B,YAAY,EAAE,cAAc,EAAE,EAC9B,eAAe,EAAE,MAAM,EACvB,KAAK,EAAE,UAAU,EAAE,EACnB,YAAY,EAAE,UAAU,EACxB,cAAc,EAAE,MAAM,GAAG,IAAI,EAC7B,eAAe,EAAE,MAAM,EACvB,KAAK,GAAE,MAAU,EACjB,SAAS,GAAE,OAAe,GACzB,iBAAiB,CAmFnB"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Module 7: InstructionResolver
|
|
3
|
+
* Resolves the next upcoming instruction and determines if it should be triggered.
|
|
4
|
+
*/
|
|
5
|
+
import { haversineDistance } from '../geo/haversine';
|
|
6
|
+
import { getBearingBetweenPoints, normalizeAngle } from '../geo/bearing';
|
|
7
|
+
import { BEARING_ALIGNMENT_THRESHOLD } from '../constants';
|
|
8
|
+
// Trigger distance constants
|
|
9
|
+
const BASE_TRIGGER_DISTANCE = 20;
|
|
10
|
+
const MAX_TRIGGER_DISTANCE = 60;
|
|
11
|
+
const SPEED_FACTOR = 3;
|
|
12
|
+
/**
|
|
13
|
+
* Resolve which instruction is next and whether it should trigger now.
|
|
14
|
+
*/
|
|
15
|
+
export function resolveNextInstruction(currentGeometryIdx, instructions, currentInstrIdx, route, currentCoord, vehicleBearing, vehicleDistance, speed = 0, isCircuit = false) {
|
|
16
|
+
if (!instructions || instructions.length === 0 || currentInstrIdx >= instructions.length) {
|
|
17
|
+
return { nextIdx: currentInstrIdx, instruction: null, wasTriggered: false };
|
|
18
|
+
}
|
|
19
|
+
let idx = currentInstrIdx;
|
|
20
|
+
// 1. Monotonic skip-ahead: consume instructions we have physically passed
|
|
21
|
+
while (idx < instructions.length) {
|
|
22
|
+
const candidate = instructions[idx];
|
|
23
|
+
const isWayBehind = currentGeometryIdx > candidate.geometryIndex + 30;
|
|
24
|
+
const isPastPoint = candidate.cumulativeOffset !== undefined &&
|
|
25
|
+
vehicleDistance > candidate.cumulativeOffset + 15;
|
|
26
|
+
if (!isWayBehind && !isPastPoint)
|
|
27
|
+
break;
|
|
28
|
+
idx++;
|
|
29
|
+
}
|
|
30
|
+
if (idx >= instructions.length) {
|
|
31
|
+
return { nextIdx: idx, instruction: null, wasTriggered: false };
|
|
32
|
+
}
|
|
33
|
+
// 2. Proximity Check
|
|
34
|
+
const next = instructions[idx];
|
|
35
|
+
const distance = next.cumulativeOffset !== undefined
|
|
36
|
+
? Math.max(0, next.cumulativeOffset - vehicleDistance)
|
|
37
|
+
: haversineDistance(currentCoord[1], currentCoord[0], next.location[1], next.location[0]);
|
|
38
|
+
const triggerDistance = Math.min(MAX_TRIGGER_DISTANCE, Math.max(BASE_TRIGGER_DISTANCE, speed * SPEED_FACTOR));
|
|
39
|
+
const isAhead = currentGeometryIdx <= next.geometryIndex + 2;
|
|
40
|
+
const isTooFarAhead = next.geometryIndex > currentGeometryIdx + (route.length * 0.15); // Don't trigger if > 15% of route ahead
|
|
41
|
+
const closeEnough = distance <= triggerDistance;
|
|
42
|
+
// Circuit logic: gate the arrival instruction (last one) until most of the route is done
|
|
43
|
+
const isLastInstruction = idx === instructions.length - 1;
|
|
44
|
+
const circuitGatePassed = (isCircuit && isLastInstruction)
|
|
45
|
+
? (currentGeometryIdx > route.length * 0.8)
|
|
46
|
+
: true;
|
|
47
|
+
const shouldTrigger = closeEnough && isAhead && !isTooFarAhead && circuitGatePassed;
|
|
48
|
+
let wasTriggered = false;
|
|
49
|
+
if (shouldTrigger) {
|
|
50
|
+
// 3. Bearing gate for significant turns
|
|
51
|
+
let gatePassed = true;
|
|
52
|
+
const bearingAfter = next.bearingAfter ??
|
|
53
|
+
(next.geometryIndex < route.length - 1
|
|
54
|
+
? getBearingBetweenPoints(route[next.geometryIndex], route[next.geometryIndex + 1])
|
|
55
|
+
: null);
|
|
56
|
+
const bearingBefore = next.bearingBefore ??
|
|
57
|
+
(next.geometryIndex > 0
|
|
58
|
+
? getBearingBetweenPoints(route[next.geometryIndex - 1], route[next.geometryIndex])
|
|
59
|
+
: null);
|
|
60
|
+
if (bearingBefore !== null && bearingAfter !== null && vehicleBearing !== null) {
|
|
61
|
+
const turnAngle = Math.abs(normalizeAngle(bearingAfter - bearingBefore));
|
|
62
|
+
if (turnAngle > 30) {
|
|
63
|
+
const bearingDiff = Math.abs(normalizeAngle(vehicleBearing - bearingAfter));
|
|
64
|
+
if (bearingDiff > BEARING_ALIGNMENT_THRESHOLD) {
|
|
65
|
+
gatePassed = false;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
if (gatePassed) {
|
|
70
|
+
wasTriggered = true;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return {
|
|
74
|
+
nextIdx: idx,
|
|
75
|
+
instruction: next,
|
|
76
|
+
wasTriggered,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Module 6: ProgressTracker
|
|
3
|
+
* Monotonic route progress tracking with anti-jitter.
|
|
4
|
+
*/
|
|
5
|
+
import { type Coordinate } from '../geo/haversine';
|
|
6
|
+
/**
|
|
7
|
+
* Validate a progress update: ensures monotonic forward progress with jitter tolerance.
|
|
8
|
+
*
|
|
9
|
+
* @returns The new progress index (may be the same as current if update is rejected)
|
|
10
|
+
*/
|
|
11
|
+
export declare function validateProgressUpdate(currentIdx: number, candidateIdx: number, route: Coordinate[], distToRoute: number, maxSnapDistance: number): number;
|
|
12
|
+
//# sourceMappingURL=ProgressTracker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ProgressTracker.d.ts","sourceRoot":"","sources":["../../../../../core/src/progress/ProgressTracker.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,KAAK,UAAU,EAAqB,MAAM,kBAAkB,CAAC;AAEtE;;;;GAIG;AACH,wBAAgB,sBAAsB,CACpC,UAAU,EAAE,MAAM,EAClB,YAAY,EAAE,MAAM,EACpB,KAAK,EAAE,UAAU,EAAE,EACnB,WAAW,EAAE,MAAM,EACnB,eAAe,EAAE,MAAM,GACtB,MAAM,CAYR"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Module 6: ProgressTracker
|
|
3
|
+
* Monotonic route progress tracking with anti-jitter.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Validate a progress update: ensures monotonic forward progress with jitter tolerance.
|
|
7
|
+
*
|
|
8
|
+
* @returns The new progress index (may be the same as current if update is rejected)
|
|
9
|
+
*/
|
|
10
|
+
export function validateProgressUpdate(currentIdx, candidateIdx, route, distToRoute, maxSnapDistance) {
|
|
11
|
+
// Never go backwards
|
|
12
|
+
if (candidateIdx < currentIdx)
|
|
13
|
+
return currentIdx;
|
|
14
|
+
// Don't jump too far forward in one tick (anti-teleport)
|
|
15
|
+
const maxJump = 30;
|
|
16
|
+
if (candidateIdx > currentIdx + maxJump)
|
|
17
|
+
return currentIdx;
|
|
18
|
+
// Don't accept if too far from route
|
|
19
|
+
if (distToRoute > maxSnapDistance)
|
|
20
|
+
return currentIdx;
|
|
21
|
+
return candidateIdx;
|
|
22
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MapRendererAdapter — Interface for map renderer integrations.
|
|
3
|
+
*
|
|
4
|
+
* Implement this interface to connect NavCore's navigation state
|
|
5
|
+
* to any map library (MapLibre, Leaflet, Google Maps, etc.).
|
|
6
|
+
*
|
|
7
|
+
* NavCore itself never depends on a renderer — adapters live
|
|
8
|
+
* in separate packages and are optionally wired up by the consumer.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```typescript
|
|
12
|
+
* import { MapLibreAdapter } from '@ingissa/navcore-maplibre';
|
|
13
|
+
*
|
|
14
|
+
* const adapter = new MapLibreAdapter(mapInstance);
|
|
15
|
+
* engine.on('stateUpdate', (state) => adapter.updateVehicle(state.snappedCoord, state.bearing, state));
|
|
16
|
+
* adapter.drawRoute(routeGeometry);
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
import type { Coordinate } from '../geo/haversine';
|
|
20
|
+
import type { NavCoreState } from '../NavCore';
|
|
21
|
+
export interface RouteStyle {
|
|
22
|
+
/** Line color as CSS color string. Default: '#7c3aed' */
|
|
23
|
+
color?: string;
|
|
24
|
+
/** Line width in pixels. Default: 4 */
|
|
25
|
+
width?: number;
|
|
26
|
+
/** Line opacity 0–1. Default: 0.9 */
|
|
27
|
+
opacity?: number;
|
|
28
|
+
/** Dash array pattern (pixels). Undefined = solid. */
|
|
29
|
+
dashArray?: number[];
|
|
30
|
+
}
|
|
31
|
+
export interface VehicleStyle {
|
|
32
|
+
/** Icon size multiplier. Default: 1.0 */
|
|
33
|
+
scale?: number;
|
|
34
|
+
/** Custom icon URL (if undefined, adapter uses its default icon) */
|
|
35
|
+
iconUrl?: string;
|
|
36
|
+
}
|
|
37
|
+
export interface CameraOptions {
|
|
38
|
+
/** Zoom level (map-library specific). Default: adapter decides. */
|
|
39
|
+
zoom?: number;
|
|
40
|
+
/** Pitch/tilt in degrees. Default: 0. */
|
|
41
|
+
pitch?: number;
|
|
42
|
+
/** Whether to animate the camera transition. Default: true. */
|
|
43
|
+
animate?: boolean;
|
|
44
|
+
/** Animation duration in ms. Default: 300. */
|
|
45
|
+
animationDurationMs?: number;
|
|
46
|
+
}
|
|
47
|
+
export interface MapRendererAdapter {
|
|
48
|
+
/**
|
|
49
|
+
* Update the vehicle marker position and rotation.
|
|
50
|
+
* Called on every NavCore state update.
|
|
51
|
+
*/
|
|
52
|
+
updateVehicle(coord: Coordinate, bearing: number, state: NavCoreState, style?: VehicleStyle): void;
|
|
53
|
+
/**
|
|
54
|
+
* Draw or update the route polyline on the map.
|
|
55
|
+
* Replaces any previously drawn route.
|
|
56
|
+
*/
|
|
57
|
+
drawRoute(coords: Coordinate[], style?: RouteStyle): void;
|
|
58
|
+
/**
|
|
59
|
+
* Remove the route polyline and vehicle marker from the map.
|
|
60
|
+
*/
|
|
61
|
+
clearRoute(): void;
|
|
62
|
+
/**
|
|
63
|
+
* Pan (and optionally rotate/tilt) the camera to follow the vehicle.
|
|
64
|
+
*/
|
|
65
|
+
panCamera(coord: Coordinate, bearing: number, options?: CameraOptions): void;
|
|
66
|
+
/**
|
|
67
|
+
* Clean up all map layers, markers, and event listeners created by this adapter.
|
|
68
|
+
* Call when navigation ends or the component unmounts.
|
|
69
|
+
*/
|
|
70
|
+
destroy(): void;
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=MapRendererAdapter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MapRendererAdapter.d.ts","sourceRoot":"","sources":["../../../../../core/src/renderer/MapRendererAdapter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAI/C,MAAM,WAAW,UAAU;IACzB,yDAAyD;IACzD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,uCAAuC;IACvC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,qCAAqC;IACrC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,sDAAsD;IACtD,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;CACtB;AAED,MAAM,WAAW,YAAY;IAC3B,yCAAyC;IACzC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,oEAAoE;IACpE,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,aAAa;IAC5B,mEAAmE;IACnE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,yCAAyC;IACzC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,+DAA+D;IAC/D,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,8CAA8C;IAC9C,mBAAmB,CAAC,EAAE,MAAM,CAAC;CAC9B;AAID,MAAM,WAAW,kBAAkB;IACjC;;;OAGG;IACH,aAAa,CACX,KAAK,EAAE,UAAU,EACjB,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,YAAY,EACnB,KAAK,CAAC,EAAE,YAAY,GACnB,IAAI,CAAC;IAER;;;OAGG;IACH,SAAS,CAAC,MAAM,EAAE,UAAU,EAAE,EAAE,KAAK,CAAC,EAAE,UAAU,GAAG,IAAI,CAAC;IAE1D;;OAEG;IACH,UAAU,IAAI,IAAI,CAAC;IAEnB;;OAEG;IACH,SAAS,CAAC,KAAK,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,IAAI,CAAC;IAE7E;;;OAGG;IACH,OAAO,IAAI,IAAI,CAAC;CACjB"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MapRendererAdapter — Interface for map renderer integrations.
|
|
3
|
+
*
|
|
4
|
+
* Implement this interface to connect NavCore's navigation state
|
|
5
|
+
* to any map library (MapLibre, Leaflet, Google Maps, etc.).
|
|
6
|
+
*
|
|
7
|
+
* NavCore itself never depends on a renderer — adapters live
|
|
8
|
+
* in separate packages and are optionally wired up by the consumer.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```typescript
|
|
12
|
+
* import { MapLibreAdapter } from '@ingissa/navcore-maplibre';
|
|
13
|
+
*
|
|
14
|
+
* const adapter = new MapLibreAdapter(mapInstance);
|
|
15
|
+
* engine.on('stateUpdate', (state) => adapter.updateVehicle(state.snappedCoord, state.bearing, state));
|
|
16
|
+
* adapter.drawRoute(routeGeometry);
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
export {};
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Module 10: CustomRouteBuilder
|
|
3
|
+
* Build custom routes with unlimited waypoints, chunking, and GPX/GeoJSON.
|
|
4
|
+
*/
|
|
5
|
+
import type { Coordinate } from '../geo/haversine';
|
|
6
|
+
export interface WaypointOptions {
|
|
7
|
+
silent?: boolean;
|
|
8
|
+
name?: string;
|
|
9
|
+
}
|
|
10
|
+
export interface BuildOptions {
|
|
11
|
+
connectionMode?: 'ROUTED' | 'STRAIGHT';
|
|
12
|
+
chunkStrategy?: 'OVERLAP_1' | 'OVERLAP_2';
|
|
13
|
+
maxWaypointsPerChunk?: number;
|
|
14
|
+
}
|
|
15
|
+
export interface CustomWaypoint {
|
|
16
|
+
id: string;
|
|
17
|
+
coord: Coordinate;
|
|
18
|
+
options?: WaypointOptions;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* CustomRouteBuilder — builds exam routes from waypoints.
|
|
22
|
+
*
|
|
23
|
+
* Pass a DirectionsProvider to `build()` for routed connections.
|
|
24
|
+
* Without a provider, `build()` returns straight-line geometry.
|
|
25
|
+
*/
|
|
26
|
+
export declare class CustomRouteBuilder {
|
|
27
|
+
private waypoints;
|
|
28
|
+
/** Add a waypoint at the end. Returns the generated ID. */
|
|
29
|
+
addWaypoint(coord: Coordinate, options?: WaypointOptions): string;
|
|
30
|
+
/** Insert a waypoint after an existing one. Returns the new ID. */
|
|
31
|
+
insertWaypoint(coord: Coordinate, afterId: string): string;
|
|
32
|
+
/** Remove a waypoint by ID */
|
|
33
|
+
removeWaypoint(id: string): void;
|
|
34
|
+
/** Move a waypoint to a new coordinate */
|
|
35
|
+
moveWaypoint(id: string, newCoord: Coordinate): void;
|
|
36
|
+
/** Get all current waypoints */
|
|
37
|
+
getWaypoints(): ReadonlyArray<CustomWaypoint>;
|
|
38
|
+
/** Get the number of waypoints */
|
|
39
|
+
get size(): number;
|
|
40
|
+
/**
|
|
41
|
+
* Chunk waypoints for a provider with max waypoint limits.
|
|
42
|
+
* OVERLAP_1: adjacent chunks share 1 waypoint (last of prev = first of next)
|
|
43
|
+
* OVERLAP_2: adjacent chunks share 2 waypoints
|
|
44
|
+
*/
|
|
45
|
+
chunk(strategy?: 'OVERLAP_1' | 'OVERLAP_2', maxPerChunk?: number): Coordinate[][];
|
|
46
|
+
/**
|
|
47
|
+
* Build straight-line geometry (no provider needed).
|
|
48
|
+
* For routed geometry, pass chunks to a DirectionsProvider externally.
|
|
49
|
+
*/
|
|
50
|
+
buildStraightLine(): {
|
|
51
|
+
geometry: Coordinate[];
|
|
52
|
+
waypoints: CustomWaypoint[];
|
|
53
|
+
};
|
|
54
|
+
/** Import waypoints from a GPX string */
|
|
55
|
+
fromGPX(gpx: string): void;
|
|
56
|
+
/** Export the current waypoints as a GPX string */
|
|
57
|
+
toGPX(): string;
|
|
58
|
+
/** Import waypoints from GeoJSON */
|
|
59
|
+
fromGeoJSON(geojson: {
|
|
60
|
+
type: string;
|
|
61
|
+
features?: Array<{
|
|
62
|
+
geometry: {
|
|
63
|
+
type: string;
|
|
64
|
+
coordinates: unknown;
|
|
65
|
+
};
|
|
66
|
+
}>;
|
|
67
|
+
}): void;
|
|
68
|
+
/** Export the current route as GeoJSON FeatureCollection */
|
|
69
|
+
toGeoJSON(): {
|
|
70
|
+
type: 'FeatureCollection';
|
|
71
|
+
features: Array<{
|
|
72
|
+
type: 'Feature';
|
|
73
|
+
geometry: {
|
|
74
|
+
type: string;
|
|
75
|
+
coordinates: Coordinate | Coordinate[];
|
|
76
|
+
};
|
|
77
|
+
properties: Record<string, unknown>;
|
|
78
|
+
}>;
|
|
79
|
+
};
|
|
80
|
+
/** Clear all waypoints */
|
|
81
|
+
clear(): void;
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=CustomRouteBuilder.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CustomRouteBuilder.d.ts","sourceRoot":"","sources":["../../../../../core/src/route-builder/CustomRouteBuilder.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAEnD,MAAM,WAAW,eAAe;IAC9B,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,YAAY;IAC3B,cAAc,CAAC,EAAE,QAAQ,GAAG,UAAU,CAAC;IACvC,aAAa,CAAC,EAAE,WAAW,GAAG,WAAW,CAAC;IAC1C,oBAAoB,CAAC,EAAE,MAAM,CAAC;CAC/B;AAED,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,UAAU,CAAC;IAClB,OAAO,CAAC,EAAE,eAAe,CAAC;CAC3B;AAOD;;;;;GAKG;AACH,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,SAAS,CAAwB;IAEzC,2DAA2D;IAC3D,WAAW,CAAC,KAAK,EAAE,UAAU,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG,MAAM;IAMjE,mEAAmE;IACnE,cAAc,CAAC,KAAK,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM;IAS1D,8BAA8B;IAC9B,cAAc,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI;IAMhC,0CAA0C;IAC1C,YAAY,CAAC,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,UAAU,GAAG,IAAI;IAMpD,gCAAgC;IAChC,YAAY,IAAI,aAAa,CAAC,cAAc,CAAC;IAI7C,kCAAkC;IAClC,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED;;;;OAIG;IACH,KAAK,CACH,QAAQ,GAAE,WAAW,GAAG,WAAyB,EACjD,WAAW,GAAE,MAAW,GACvB,UAAU,EAAE,EAAE;IAiBjB;;;OAGG;IACH,iBAAiB,IAAI;QAAE,QAAQ,EAAE,UAAU,EAAE,CAAC;QAAC,SAAS,EAAE,cAAc,EAAE,CAAA;KAAE;IAO5E,yCAAyC;IACzC,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAgB1B,mDAAmD;IACnD,KAAK,IAAI,MAAM;IAaf,oCAAoC;IACpC,WAAW,CAAC,OAAO,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,KAAK,CAAC;YAAE,QAAQ,EAAE;gBAAE,IAAI,EAAE,MAAM,CAAC;gBAAC,WAAW,EAAE,OAAO,CAAA;aAAE,CAAA;SAAE,CAAC,CAAA;KAAE,GAAG,IAAI;IAkBpH,4DAA4D;IAC5D,SAAS,IAAI;QAAE,IAAI,EAAE,mBAAmB,CAAC;QAAC,QAAQ,EAAE,KAAK,CAAC;YAAE,IAAI,EAAE,SAAS,CAAC;YAAC,QAAQ,EAAE;gBAAE,IAAI,EAAE,MAAM,CAAC;gBAAC,WAAW,EAAE,UAAU,GAAG,UAAU,EAAE,CAAA;aAAE,CAAC;YAAC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;SAAE,CAAC,CAAA;KAAE;IA8BzL,0BAA0B;IAC1B,KAAK,IAAI,IAAI;CAGd"}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Module 10: CustomRouteBuilder
|
|
3
|
+
* Build custom routes with unlimited waypoints, chunking, and GPX/GeoJSON.
|
|
4
|
+
*/
|
|
5
|
+
let _counter = 0;
|
|
6
|
+
function generateId() {
|
|
7
|
+
return `wp_${Date.now()}_${++_counter}`;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* CustomRouteBuilder — builds exam routes from waypoints.
|
|
11
|
+
*
|
|
12
|
+
* Pass a DirectionsProvider to `build()` for routed connections.
|
|
13
|
+
* Without a provider, `build()` returns straight-line geometry.
|
|
14
|
+
*/
|
|
15
|
+
export class CustomRouteBuilder {
|
|
16
|
+
constructor() {
|
|
17
|
+
this.waypoints = [];
|
|
18
|
+
}
|
|
19
|
+
/** Add a waypoint at the end. Returns the generated ID. */
|
|
20
|
+
addWaypoint(coord, options) {
|
|
21
|
+
const id = generateId();
|
|
22
|
+
this.waypoints.push({ id, coord, options });
|
|
23
|
+
return id;
|
|
24
|
+
}
|
|
25
|
+
/** Insert a waypoint after an existing one. Returns the new ID. */
|
|
26
|
+
insertWaypoint(coord, afterId) {
|
|
27
|
+
const idx = this.waypoints.findIndex(w => w.id === afterId);
|
|
28
|
+
if (idx === -1)
|
|
29
|
+
throw new Error(`Waypoint ${afterId} not found`);
|
|
30
|
+
const id = generateId();
|
|
31
|
+
this.waypoints.splice(idx + 1, 0, { id, coord, options: undefined });
|
|
32
|
+
return id;
|
|
33
|
+
}
|
|
34
|
+
/** Remove a waypoint by ID */
|
|
35
|
+
removeWaypoint(id) {
|
|
36
|
+
const idx = this.waypoints.findIndex(w => w.id === id);
|
|
37
|
+
if (idx === -1)
|
|
38
|
+
throw new Error(`Waypoint ${id} not found`);
|
|
39
|
+
this.waypoints.splice(idx, 1);
|
|
40
|
+
}
|
|
41
|
+
/** Move a waypoint to a new coordinate */
|
|
42
|
+
moveWaypoint(id, newCoord) {
|
|
43
|
+
const wp = this.waypoints.find(w => w.id === id);
|
|
44
|
+
if (!wp)
|
|
45
|
+
throw new Error(`Waypoint ${id} not found`);
|
|
46
|
+
wp.coord = newCoord;
|
|
47
|
+
}
|
|
48
|
+
/** Get all current waypoints */
|
|
49
|
+
getWaypoints() {
|
|
50
|
+
return this.waypoints;
|
|
51
|
+
}
|
|
52
|
+
/** Get the number of waypoints */
|
|
53
|
+
get size() {
|
|
54
|
+
return this.waypoints.length;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Chunk waypoints for a provider with max waypoint limits.
|
|
58
|
+
* OVERLAP_1: adjacent chunks share 1 waypoint (last of prev = first of next)
|
|
59
|
+
* OVERLAP_2: adjacent chunks share 2 waypoints
|
|
60
|
+
*/
|
|
61
|
+
chunk(strategy = 'OVERLAP_1', maxPerChunk = 25) {
|
|
62
|
+
const coords = this.waypoints.map(w => w.coord);
|
|
63
|
+
if (coords.length <= maxPerChunk)
|
|
64
|
+
return [coords];
|
|
65
|
+
const overlap = strategy === 'OVERLAP_2' ? 2 : 1;
|
|
66
|
+
const step = maxPerChunk - overlap;
|
|
67
|
+
const chunks = [];
|
|
68
|
+
for (let i = 0; i < coords.length; i += step) {
|
|
69
|
+
const chunk = coords.slice(i, i + maxPerChunk);
|
|
70
|
+
chunks.push(chunk);
|
|
71
|
+
if (i + maxPerChunk >= coords.length)
|
|
72
|
+
break;
|
|
73
|
+
}
|
|
74
|
+
return chunks;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Build straight-line geometry (no provider needed).
|
|
78
|
+
* For routed geometry, pass chunks to a DirectionsProvider externally.
|
|
79
|
+
*/
|
|
80
|
+
buildStraightLine() {
|
|
81
|
+
return {
|
|
82
|
+
geometry: this.waypoints.map(w => w.coord),
|
|
83
|
+
waypoints: [...this.waypoints],
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
/** Import waypoints from a GPX string */
|
|
87
|
+
fromGPX(gpx) {
|
|
88
|
+
this.waypoints = [];
|
|
89
|
+
// Minimal GPX parser — extract <trkpt> and <wpt> elements
|
|
90
|
+
const pointRegex = /<(?:trkpt|wpt|rtept)\s+lat="([^"]+)"\s+lon="([^"]+)"/g;
|
|
91
|
+
let match;
|
|
92
|
+
while ((match = pointRegex.exec(gpx)) !== null) {
|
|
93
|
+
const lat = parseFloat(match[1]);
|
|
94
|
+
const lon = parseFloat(match[2]);
|
|
95
|
+
if (isFinite(lat) && isFinite(lon)) {
|
|
96
|
+
this.addWaypoint([lon, lat]);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
/** Export the current waypoints as a GPX string */
|
|
101
|
+
toGPX() {
|
|
102
|
+
const points = this.waypoints.map(w => ` <wpt lat="${w.coord[1]}" lon="${w.coord[0]}">` +
|
|
103
|
+
(w.options?.name ? `\n <name>${w.options.name}</name>` : '') +
|
|
104
|
+
`\n </wpt>`).join('\n');
|
|
105
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
106
|
+
<gpx version="1.1" creator="NavCore SDK">
|
|
107
|
+
${points}
|
|
108
|
+
</gpx>`;
|
|
109
|
+
}
|
|
110
|
+
/** Import waypoints from GeoJSON */
|
|
111
|
+
fromGeoJSON(geojson) {
|
|
112
|
+
this.waypoints = [];
|
|
113
|
+
if (geojson.type === 'FeatureCollection' && geojson.features) {
|
|
114
|
+
for (const feature of geojson.features) {
|
|
115
|
+
if (feature.geometry.type === 'Point') {
|
|
116
|
+
const coord = feature.geometry.coordinates;
|
|
117
|
+
this.addWaypoint(coord);
|
|
118
|
+
}
|
|
119
|
+
else if (feature.geometry.type === 'LineString') {
|
|
120
|
+
const coords = feature.geometry.coordinates;
|
|
121
|
+
for (const coord of coords) {
|
|
122
|
+
this.addWaypoint(coord);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
/** Export the current route as GeoJSON FeatureCollection */
|
|
129
|
+
toGeoJSON() {
|
|
130
|
+
const pointFeatures = this.waypoints.map((w, i) => ({
|
|
131
|
+
type: 'Feature',
|
|
132
|
+
geometry: {
|
|
133
|
+
type: 'Point',
|
|
134
|
+
coordinates: w.coord,
|
|
135
|
+
},
|
|
136
|
+
properties: {
|
|
137
|
+
id: w.id,
|
|
138
|
+
order: i,
|
|
139
|
+
name: w.options?.name ?? null,
|
|
140
|
+
silent: w.options?.silent ?? false,
|
|
141
|
+
},
|
|
142
|
+
}));
|
|
143
|
+
const lineFeature = {
|
|
144
|
+
type: 'Feature',
|
|
145
|
+
geometry: {
|
|
146
|
+
type: 'LineString',
|
|
147
|
+
coordinates: this.waypoints.map(w => w.coord),
|
|
148
|
+
},
|
|
149
|
+
properties: { type: 'route' },
|
|
150
|
+
};
|
|
151
|
+
return {
|
|
152
|
+
type: 'FeatureCollection',
|
|
153
|
+
features: [...pointFeatures, lineFeature],
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
/** Clear all waypoints */
|
|
157
|
+
clear() {
|
|
158
|
+
this.waypoints = [];
|
|
159
|
+
}
|
|
160
|
+
}
|