@ingissa/navcore-core 1.0.3 → 1.0.5

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/dist/pro.d.mts CHANGED
@@ -1,488 +1,2 @@
1
- import { C as Coordinate, i as NavInstruction, g as NavCoreState } from './NavCore.d-BuXR5DO7.mjs';
2
-
3
- /**
4
- * ParallelRoadResolver
5
- *
6
- * First-class feature: resolve GPS ambiguity on parallel roads (e.g. dual carriageways,
7
- * motorway + service road) and prevent U-turn snap.
8
- *
9
- * Extracts and extends the bearing penalty + inertia logic from RouteSnapper
10
- * into a standalone, composable API that can be used independently.
11
- *
12
- * FEATURE: PARALLEL_ROAD_RESOLUTION + U_TURN_PROTECTION
13
- *
14
- * @example
15
- * ```typescript
16
- * const resolver = new ParallelRoadResolver({ bearingPenalty: 80, inertiaPenalty: 40 });
17
- * const best = resolver.resolve(candidates, vehicleBearing, prevSegmentIndex);
18
- * ```
19
- */
20
-
21
- interface SnapCandidate {
22
- /** Route segment index */
23
- segmentIndex: number;
24
- /** Projected point on segment */
25
- projectedCoord: Coordinate;
26
- /** Raw distance to route in metres */
27
- distanceMetres: number;
28
- /** Start of segment */
29
- segA: Coordinate;
30
- /** End of segment */
31
- segB: Coordinate;
32
- }
33
- interface ResolvedCandidate extends SnapCandidate {
34
- /** Final score (lower is better) */
35
- score: number;
36
- /** Bearing delta that triggered U-turn protection (null if not triggered) */
37
- bearingDeltaDeg: number | null;
38
- /** Whether inertia penalty was applied */
39
- inertiaPenaltyApplied: boolean;
40
- }
41
- interface ParallelRoadResolverOptions {
42
- /**
43
- * Penalty added to score when vehicle bearing mismatches segment bearing
44
- * beyond `bearingMismatchThresholdDeg`. Default: 80m equivalent.
45
- */
46
- bearingPenalty?: number;
47
- /**
48
- * Bearing mismatch threshold in degrees that activates the penalty.
49
- * Default: 60°. Higher = more tolerant of U-turns.
50
- */
51
- bearingMismatchThresholdDeg?: number;
52
- /**
53
- * Penalty added when the candidate segment is far from the previously
54
- * snapped segment, preventing oscillation. Default: 40m equivalent.
55
- */
56
- inertiaPenalty?: number;
57
- /**
58
- * Minimum segment jump (in index units) to trigger inertia penalty.
59
- * Default: 3.
60
- */
61
- inertiaSegmentDist?: number;
62
- }
63
- declare class ParallelRoadResolver {
64
- private readonly options;
65
- constructor(options?: ParallelRoadResolverOptions);
66
- private warned;
67
- /**
68
- * Score and sort snap candidates — lowest score wins.
69
- *
70
- * @param candidates Raw snap candidates (distance-sorted or unsorted)
71
- * @param vehicleBearing Current vehicle heading in degrees (null disables bearing check)
72
- * @param prevSegmentIdx Previously snapped segment index (null disables inertia)
73
- * @returns Candidates sorted by score ascending, with debug metadata attached
74
- */
75
- resolve(candidates: SnapCandidate[], vehicleBearing?: number | null, prevSegmentIdx?: number | null): ResolvedCandidate[];
76
- /**
77
- * Resolve and return only the best candidate.
78
- */
79
- resolveBest(candidates: SnapCandidate[], vehicleBearing?: number | null, prevSegmentIdx?: number | null): ResolvedCandidate | null;
80
- private score;
81
- /**
82
- * Quick utility: detect if the vehicle is likely performing a U-turn
83
- * based on the difference between current bearing and route segment bearing.
84
- *
85
- * @returns true if the bearing difference exceeds the configured threshold
86
- */
87
- isLikelyUTurn(vehicleBearing: number, segA: Coordinate, segB: Coordinate): boolean;
88
- }
89
-
90
- /**
91
- * Module 3: DeadReckoning
92
- * Predicts vehicle position during GPS outages or stale fixes.
93
- */
94
-
95
- interface DRAnchor {
96
- coord: Coordinate;
97
- idx: number;
98
- ts: number;
99
- }
100
- interface DRPrediction {
101
- predictedCoord: Coordinate;
102
- isActive: boolean;
103
- ageMs: number;
104
- }
105
- /**
106
- * Given a snapped anchor on the route, extrapolate forward using speed and bearing.
107
- * Note: This function is a pure utility; license enforcement is in DeadReckoningEngine.predict().
108
- */
109
- declare function computeDeadReckoningOnRoute(anchor: DRAnchor | null, speed: number, bearing: number, now: number, maxAgeMs?: number): DRPrediction;
110
- /**
111
- * Stateful dead reckoning engine.
112
- *
113
- * FEATURE: DEAD_RECKONING (Pro license required)
114
- * predict() is a no-op and returns isActive=false when no valid Pro license is present.
115
- */
116
- declare class DeadReckoningEngine {
117
- private anchor;
118
- private readonly maxAgeMs;
119
- private warned;
120
- constructor(maxAgeMs?: number);
121
- /** Set the anchor point (called when GPS is available and snapping succeeds) */
122
- setAnchor(coord: Coordinate, idx: number, timestamp: number): void;
123
- /**
124
- * Predict position given current speed and bearing.
125
- * Requires a valid Pro license — returns isActive=false without one.
126
- */
127
- predict(speed: number, bearing: number, now: number): DRPrediction;
128
- /** Clear the anchor (GPS restored) */
129
- clearAnchor(): void;
130
- getAnchor(): DRAnchor | null;
131
- }
132
-
133
- /**
134
- * Module 4b: DeviationDetector
135
- * Compound deviation detection using distance, bearing, and movement signals.
136
- *
137
- * FEATURE: COMPOUND_DEVIATION (Pro license required)
138
- * evaluate() returns a neutral "no-deviation" signal without a valid Pro license.
139
- */
140
-
141
- interface DeviationSignals {
142
- /** Distance from GPS to nearest route segment (metres) */
143
- distanceToRoute: number;
144
- /** Whether the distance exceeds the dynamic corridor */
145
- outsideCorridor: boolean;
146
- /** Whether the vehicle bearing diverges from the route bearing */
147
- bearingMismatch: boolean;
148
- /** Whether the vehicle is moving away from the next segment */
149
- movingAway: boolean;
150
- /** Compound confirmed: outsideCorridor AND (bearingMismatch OR movingAway) */
151
- confirmed: boolean;
152
- }
153
- interface ClosestSegment {
154
- segmentIndex: number;
155
- distance: number;
156
- projection: Coordinate;
157
- }
158
- declare class DeviationDetector {
159
- private warned;
160
- /**
161
- * Evaluate deviation signals for the current GPS position.
162
- * Requires a valid Pro license — returns a neutral signal without one.
163
- */
164
- evaluate(gps: Coordinate, gpsBearing: number | null, speedMps: number, closest: ClosestSegment, route: Coordinate[], corridor: number, prevDistToNextSegment: number | null): DeviationSignals;
165
- }
166
-
167
- /**
168
- * Module 4a: DynamicCorridor
169
- * Speed-adaptive corridor width calculation.
170
- */
171
- /**
172
- * Compute speed-adaptive corridor width in metres.
173
- *
174
- * Corridor widens with speed: BASE + speed * FACTOR, capped at MAX.
175
- * GPS accuracy optionally widens the corridor further.
176
- */
177
- declare function calculateDynamicCorridor(speedMps: number, gpsAccuracy?: number | null, baseMeters?: number, maxMeters?: number, speedFactor?: number, accuracyFactor?: number): number;
178
-
179
- /**
180
- * InstructionEditor — Fluent builder for NavInstruction arrays.
181
- *
182
- * Create, edit, and annotate navigation instructions for custom routes.
183
- * Supports standard turn instructions and exam-specific annotations.
184
- *
185
- * @example
186
- * ```typescript
187
- * const editor = new InstructionEditor();
188
- * editor
189
- * .addTurn([2.347, 48.859], 'Turn left onto Rue de Rivoli', 42)
190
- * .addExamPoint([2.350, 48.860], 'Check mirrors before changing lane', 50)
191
- * .addCheckpoint([2.355, 48.861], 65);
192
- *
193
- * editor.attachToCumulative(routeGeometry);
194
- * engine.setRoute(routeGeometry, editor.toArray());
195
- * ```
196
- */
197
-
198
- type InstructionType = 'turn' | 'exam_point' | 'checkpoint' | 'depart' | 'arrive' | 'custom';
199
- interface RichInstruction extends NavInstruction {
200
- /** Instruction category */
201
- type: InstructionType;
202
- /** Unique id for referencing in edit operations */
203
- id: string;
204
- /** Display text (same as NavInstruction.text) */
205
- text: string;
206
- /** Whether to suppress voice read-out */
207
- silent?: boolean;
208
- /** Exam-specific severity level */
209
- severity?: 'info' | 'warning' | 'critical';
210
- /** Custom metadata for integrators */
211
- meta?: Record<string, unknown>;
212
- }
213
- declare class InstructionEditor {
214
- private instructions;
215
- /** Add a turn instruction at the given route geometry index */
216
- addTurn(location: Coordinate, text: string, geometryIndex: number, opts?: {
217
- silent?: boolean;
218
- meta?: Record<string, unknown>;
219
- }): this;
220
- /** Add an exam-specific point (e.g. "Check mirrors", "Yield here") */
221
- addExamPoint(location: Coordinate, text: string, geometryIndex: number, opts?: {
222
- severity?: RichInstruction['severity'];
223
- silent?: boolean;
224
- meta?: Record<string, unknown>;
225
- }): this;
226
- /** Add a silent progress checkpoint (does not trigger voice) */
227
- addCheckpoint(location: Coordinate, geometryIndex: number, meta?: Record<string, unknown>): this;
228
- /** Add a departure instruction (auto-inserted at index 0) */
229
- addDepart(location: Coordinate, text?: string): this;
230
- /** Add an arrival instruction */
231
- addArrive(location: Coordinate, text?: string): this;
232
- /** Update any field of an instruction by id */
233
- update(id: string, patch: Partial<Omit<RichInstruction, 'id'>>): this;
234
- /** Remove an instruction by id */
235
- remove(id: string): this;
236
- /** Move an instruction to a new position in the list */
237
- reorder(id: string, newIndex: number): this;
238
- /** Sort instructions by geometryIndex ascending */
239
- sort(): this;
240
- /** Remove all instructions */
241
- clear(): this;
242
- /**
243
- * Compute and attach `cumulativeOffset` and `bearingBefore`/`bearingAfter`
244
- * from the route geometry. Call this after loading the route but before
245
- * passing instructions to the engine.
246
- */
247
- attachToCumulative(route: Coordinate[]): this;
248
- /** Export as plain NavInstruction[] for use with engine.setRoute() */
249
- toArray(): NavInstruction[];
250
- /** Export rich instructions with all metadata */
251
- toRichArray(): ReadonlyArray<RichInstruction>;
252
- get size(): number;
253
- }
254
-
255
- /**
256
- * VoiceTriggerEngine
257
- *
258
- * Converts NavCore instruction events into timed voice cues.
259
- * Handles deduplication, look-ahead windows, and priority levels.
260
- * Consumer calls TTS — this engine only computes WHEN to speak.
261
- *
262
- * FEATURE: VOICE_TRIGGERS
263
- *
264
- * @example
265
- * ```typescript
266
- * const voice = new VoiceTriggerEngine({ earlyTriggerMeters: 150 });
267
- * voice.setInstructions(instructions);
268
- *
269
- * engine.on('instruction', (instr) => {
270
- * const cue = voice.onInstruction(instr, state);
271
- * if (cue) speakText(cue.text);
272
- * });
273
- *
274
- * // Or poll each GPS tick:
275
- * const cue = voice.update(state);
276
- * if (cue) speakText(cue.text);
277
- * ```
278
- */
279
-
280
- type VoicePriority = 'low' | 'normal' | 'high' | 'urgent';
281
- interface VoiceCue {
282
- /** The instruction that triggered this cue */
283
- instruction: NavInstruction;
284
- /** Text to pass to TTS */
285
- text: string;
286
- /** Distance to the instruction point in metres */
287
- distanceMetres: number;
288
- /** Priority hint for TTS queue management */
289
- priority: VoicePriority;
290
- /** Monotonic trigger ID — use this to deduplicate */
291
- triggerId: string;
292
- }
293
- interface VoiceTriggerOptions {
294
- /**
295
- * Trigger voice this many metres before the instruction point.
296
- * Default: 120m (comfortable at 50 km/h).
297
- */
298
- earlyTriggerMeters?: number;
299
- /**
300
- * Stop trying to trigger if vehicle is already past this many metres.
301
- * Default: 20m.
302
- */
303
- lateTriggerMeters?: number;
304
- /**
305
- * Re-announce if user is still approaching after this distance.
306
- * Set to 0 to disable re-announcement. Default: 300m.
307
- */
308
- reannounceMeters?: number;
309
- }
310
- declare class VoiceTriggerEngine {
311
- private instructions;
312
- private triggeredIds;
313
- private reannouncedIds;
314
- private readonly options;
315
- constructor(options?: VoiceTriggerOptions);
316
- /** Load the instruction set (call after engine.setRoute) */
317
- setInstructions(instructions: NavInstruction[]): void;
318
- /**
319
- * Poll on every GPS state update.
320
- * Returns a `VoiceCue` when an instruction should be spoken, or null.
321
- */
322
- update(state: NavCoreState): VoiceCue | null;
323
- /**
324
- * Called directly from engine's 'instruction' event.
325
- * Always fires immediately regardless of distance window.
326
- */
327
- onInstruction(instr: NavInstruction, state: NavCoreState): VoiceCue;
328
- /** Reset triggered state (call on setRoute / new journey) */
329
- reset(): void;
330
- private instrId;
331
- private makeCue;
332
- }
333
-
334
- /**
335
- * ETAEngine — Local ETA computation. Zero API calls.
336
- *
337
- * Computes Estimated Time of Arrival from current NavCore state using
338
- * a rolling speed average. No server required — entirely offline.
339
- *
340
- * FEATURE: ETA
341
- *
342
- * @example
343
- * ```typescript
344
- * const eta = new ETAEngine({ speedWindowSize: 8 });
345
- * engine.on('update', (state) => {
346
- * const result = eta.update(state);
347
- * console.log(`ETA: ${result.etaSeconds}s, ${result.distanceRemainingM.toFixed(0)}m`);
348
- * });
349
- * ```
350
- */
351
-
352
- interface ETAResult {
353
- /** ETA in seconds from now */
354
- etaSeconds: number;
355
- /** ETA as absolute timestamp (Date.now() + etaSeconds * 1000) */
356
- etaMs: number;
357
- /** Remaining distance in metres */
358
- distanceRemainingM: number;
359
- /** Rolling average speed in m/s */
360
- averageSpeedMs: number;
361
- /** Whether the estimate is reliable (enough speed samples + non-zero speed) */
362
- isReliable: boolean;
363
- }
364
- interface ETAOptions {
365
- /**
366
- * Number of speed samples in the rolling average window.
367
- * More samples = smoother but slower to react. Default: 6.
368
- */
369
- speedWindowSize?: number;
370
- /**
371
- * Minimum speed (m/s) below which we use the last reliable speed
372
- * rather than 0 (avoids Infinity ETA while stopped). Default: 0.5 m/s (~2 km/h).
373
- */
374
- minimumSpeedMs?: number;
375
- /**
376
- * Speed to assume if no reliable samples yet. Default: 8.3 m/s (30 km/h).
377
- */
378
- fallbackSpeedMs?: number;
379
- }
380
- declare class ETAEngine {
381
- private readonly options;
382
- private speedSamples;
383
- private lastReliableSpeed;
384
- constructor(options?: ETAOptions);
385
- /**
386
- * Feed a NavCore state snapshot and receive an ETA estimate.
387
- * Call this on every GPS update tick.
388
- */
389
- update(state: NavCoreState): ETAResult;
390
- /** Reset the speed window (call on new route or significant GPS gap) */
391
- reset(): void;
392
- private rollingAverage;
393
- }
394
-
395
- /**
396
- * GeofencingEngine
397
- *
398
- * Circular and polygon geofences — zero server dependency.
399
- * Uses haversine distance for circles and ray-casting for polygons.
400
- *
401
- * FEATURE: GEOFENCING
402
- *
403
- * @example
404
- * ```typescript
405
- * const geo = new GeofencingEngine({ dwellThresholdMs: 5000 });
406
- * geo.addCircle('school-zone', 'School Zone', [2.347, 48.859], 50, { speedLimit: 30 });
407
- * geo.addPolygon('exam-area', 'Exam District', [[2.34, 48.85], [2.36, 48.85], [2.36, 48.87], [2.34, 48.87]]);
408
- *
409
- * engine.on('update', ({ snappedCoord }) => {
410
- * const events = geo.update(snappedCoord);
411
- * for (const evt of events) {
412
- * // evt.name is the human-readable zone name
413
- * console.log(`${evt.type}: ${evt.name} (${evt.id})`);
414
- * }
415
- * });
416
- * ```
417
- */
418
-
419
- type GeofenceType = 'circle' | 'polygon';
420
- type GeofenceEventType = 'enter' | 'exit' | 'dwell';
421
- interface GeofenceEvent {
422
- /** Geofence ID that triggered the event */
423
- id: string;
424
- /** Human-readable name of the geofence */
425
- name: string;
426
- type: GeofenceEventType;
427
- /** Coordinate that triggered the event */
428
- coord: Coordinate;
429
- /** Milliseconds spent inside (for dwell events) */
430
- dwellMs?: number;
431
- /** Custom metadata passed at creation */
432
- meta?: unknown;
433
- }
434
- interface ZoneDefinition {
435
- id: string;
436
- name: string;
437
- type: 'circle' | 'polygon';
438
- center?: Coordinate;
439
- radiusMeters?: number;
440
- coords?: Coordinate[];
441
- meta?: unknown;
442
- }
443
- interface GeofencingOptions {
444
- /**
445
- * Milliseconds a vehicle must remain inside a geofence before a 'dwell' event fires.
446
- * Default: 10000ms (10 seconds).
447
- */
448
- dwellThresholdMs?: number;
449
- /** Initial zones to register on creation */
450
- zones?: ZoneDefinition[];
451
- /** Callback for all geofence events */
452
- onEvent?: (event: GeofenceEvent) => void;
453
- }
454
- declare class GeofencingEngine {
455
- private fences;
456
- private states;
457
- private readonly options;
458
- constructor(options?: GeofencingOptions);
459
- /** Add a circular geofence. Returns the ID. */
460
- addCircle(id: string, name: string, center: Coordinate, radiusM: number, meta?: unknown): string;
461
- /**
462
- * Add a polygon geofence. Coords are [lng, lat] pairs.
463
- * The polygon is auto-closed (first == last not required).
464
- */
465
- addPolygon(id: string, name: string, coords: Coordinate[], meta?: unknown): string;
466
- /** Remove a geofence by ID */
467
- remove(id: string): void;
468
- /** Remove all geofences */
469
- clear(): void;
470
- /** List all registered geofence IDs */
471
- listIds(): string[];
472
- /**
473
- * Feed the current coordinate and receive a (possibly empty) list of events.
474
- * Call this on every NavCore state update.
475
- */
476
- update(coord: Coordinate): GeofenceEvent[];
477
- /** Check if a coordinate is currently inside a named geofence */
478
- isInside(id: string, coord: Coordinate): boolean;
479
- private contains;
480
- /**
481
- * Ray-casting point-in-polygon algorithm.
482
- * Works in [lng, lat] space (sufficient for small areas; use projected
483
- * coords for very large polygons spanning many degrees).
484
- */
485
- private pointInPolygon;
486
- }
487
-
488
- export { DeadReckoningEngine, DeviationDetector, ETAEngine, type ETAOptions, type ETAResult, type GeofenceEvent, type GeofenceEventType, type GeofenceType, GeofencingEngine, type GeofencingOptions, InstructionEditor, type InstructionType, ParallelRoadResolver, type ParallelRoadResolverOptions, type ResolvedCandidate, type RichInstruction, type SnapCandidate, type VoiceCue, type VoicePriority, VoiceTriggerEngine, type VoiceTriggerOptions, type ZoneDefinition, calculateDynamicCorridor, computeDeadReckoningOnRoute };
1
+ export { D as DeadReckoningEngine, a as DeviationDetector, E as ETAEngine, b as ETAOptions, c as ETAResult, G as GeofenceEvent, d as GeofenceEventType, e as GeofenceType, f as GeofencingEngine, g as GeofencingOptions, I as InstructionEditor, h as InstructionType, P as ParallelRoadResolver, i as ParallelRoadResolverOptions, R as ResolvedCandidate, j as RichInstruction, S as SnapCandidate, V as VoiceCue, k as VoicePriority, l as VoiceTriggerEngine, m as VoiceTriggerOptions, n as advancePredictor, o as applyBearingSmoothing, p as applyGpsCorrection, q as blendBearing, r as calculateDynamicCorridor, s as calculateRobustBearing, t as computeDeadReckoningOnRoute, u as getSpeedAdaptiveAlpha, v as pointAtOffset, w as validateProgressUpdate } from './index.pro-Cg6394f_.mjs';
2
+ export { d as LifecycleContext, e as LifecycleEvent, i as NavInstruction, j as NavLifecycleState, k as NavigationFSM, O as OffRouteContext, l as OffRouteEvent, m as OffRouteFSM, n as OffRouteState, R as RejoinTarget, T as TempRoute, r as resolveNextInstruction } from './NavCore-qf5_0qsR.mjs';