@ingissa/navcore-core 1.0.0 → 1.0.2

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/README.md ADDED
@@ -0,0 +1,279 @@
1
+ # @ingissa/navcore-core
2
+
3
+ > **The navigation layer you own.** A framework-agnostic, zero-dependency navigation engine that runs everywhere — browsers, React Native, Node.js, and CI/CD.
4
+
5
+ [![npm version](https://img.shields.io/npm/v/@ingissa/navcore-core)](https://www.npmjs.com/package/@ingissa/navcore-core)
6
+ [![License](https://img.shields.io/badge/license-MIT%20%2F%20Commercial-purple)]()
7
+ [![TypeScript](https://img.shields.io/badge/TypeScript-strict-blue)](https://www.typescriptlang.org/)
8
+ [![Zero deps](https://img.shields.io/badge/runtime_deps-0-brightgreen)](https://www.npmjs.com/package/@ingissa/navcore-core)
9
+
10
+ ---
11
+
12
+ ## Why NavCore?
13
+
14
+ | Feature | NavCore | Mapbox Nav SDK | Google Maps |
15
+ |---|---|---|---|
16
+ | **Framework** | Any (pure TS) | React Native only | Platform-locked |
17
+ | **Runtime deps** | 0 | 50+ | N/A |
18
+ | **Map renderer** | BYO (adapter) | Mapbox GL only | Google Maps only |
19
+ | **Directions** | BYO (adapter) | Mapbox API only | Google API only |
20
+ | **Off-route detection** | Built-in FSM | Basic | Basic |
21
+ | **U-turn protection** | ✅ Bearing penalty | ❌ | ❌ |
22
+ | **Parallel road resolver** | ✅ Built-in | ❌ | ❌ |
23
+ | **Dead reckoning** | ✅ Built-in | ❌ | ❌ |
24
+ | **Geofencing** | ✅ Built-in | ❌ | Paid API |
25
+ | **ETA** | ✅ Offline | API only | API only |
26
+ | **Voice triggers** | ✅ Built-in | SDK only | ❌ |
27
+ | **Headless testing** | ✅ Full pipeline | ❌ | ❌ |
28
+ | **Custom routes** | ✅ GPX/GeoJSON/Builder | ❌ | ❌ |
29
+ | **License** | Core MIT / Pro commercial | Commercial | Commercial |
30
+
31
+ ---
32
+
33
+ ## Tier Model
34
+
35
+ NavCore uses a two-tier model controlled by a signed license key:
36
+
37
+ | Tier | Import | License | Includes |
38
+ |---|---|---|---|
39
+ | **Core** | `@ingissa/navcore-core/core` | MIT — free forever | Geo utils, Kalman filter, route snapping, directions adapters, route builder |
40
+ | **Pro** | `@ingissa/navcore-core/pro` | Commercial key required | Dead reckoning, advanced bearing, deviation detection, FSMs, voice, ETA, geofencing |
41
+ | **Engine** | `@ingissa/navcore-core` | Gated internally at runtime | Full `NavCore` orchestrator (license key unlocks Pro features) |
42
+
43
+ ---
44
+
45
+ ## Installation
46
+
47
+ ```bash
48
+ npm install @ingissa/navcore-core
49
+ ```
50
+
51
+ ---
52
+
53
+ ## Quick Start
54
+
55
+ ### Core tier (no license needed)
56
+
57
+ ```typescript
58
+ import { NavCore } from '@ingissa/navcore-core';
59
+ import { OSRMDirectionsProvider } from '@ingissa/navcore-core/core';
60
+
61
+ // 1. Get a route from your self-hosted OSRM (free)
62
+ const provider = new OSRMDirectionsProvider({ baseUrl: 'http://router.project-osrm.org' });
63
+ const route = await provider.getRoute([[2.3522, 48.8566], [2.3400, 48.8700]]);
64
+
65
+ // 2. Create engine (Core features work without a key)
66
+ const engine = new NavCore();
67
+ engine.setRoute(route.geometry);
68
+ engine.startNavigation();
69
+
70
+ // 3. Feed GPS updates
71
+ engine.on('arrival', () => console.log('Arrived!'));
72
+
73
+ navigator.geolocation.watchPosition(({ coords }) => {
74
+ const state = engine.update({
75
+ coord: [coords.longitude, coords.latitude],
76
+ accuracy: coords.accuracy,
77
+ bearing: coords.heading,
78
+ speed: coords.speed ?? 0,
79
+ timestamp: Date.now(),
80
+ });
81
+
82
+ console.log('Distance remaining:', state.distanceToDestination, 'm');
83
+ });
84
+ ```
85
+
86
+ ### Pro tier (with license key)
87
+
88
+ ```typescript
89
+ import { NavCore } from '@ingissa/navcore-core';
90
+
91
+ // Pro features unlock automatically with a valid key
92
+ const engine = new NavCore({ licenseKey: 'your-signed-license-key' });
93
+ engine.setRoute(routeCoords, instructions);
94
+ engine.startNavigation();
95
+
96
+ engine.on('instruction', (instr) => speak(instr.text)); // voice cues (PRO)
97
+ engine.on('deviation', ({ anchorIndex }) => recalculate(anchorIndex));
98
+ engine.on('arrival', () => console.log('Arrived!'));
99
+ ```
100
+
101
+ ### Using subpath imports directly
102
+
103
+ ```typescript
104
+ // Free MIT tier
105
+ import { KalmanFilter2D } from '@ingissa/navcore-core/core';
106
+ import { OSRMDirectionsProvider } from '@ingissa/navcore-core/core';
107
+ import { CustomRouteBuilder } from '@ingissa/navcore-core/core';
108
+
109
+ // Commercial Pro tier
110
+ import { DeadReckoningEngine } from '@ingissa/navcore-core/pro';
111
+ import { VoiceTriggerEngine } from '@ingissa/navcore-core/pro';
112
+ import { ETAEngine } from '@ingissa/navcore-core/pro';
113
+ import { GeofencingEngine } from '@ingissa/navcore-core/pro';
114
+ ```
115
+
116
+ ---
117
+
118
+ ## Directions Providers (built-in, no extra install)
119
+
120
+ ```typescript
121
+ import {
122
+ OSRMDirectionsProvider, // Self-hosted OSRM (recommended, free)
123
+ ValhallaDirectionsProvider, // Self-hosted Valhalla (free)
124
+ OpenRouteServiceProvider, // openrouteservice.org (free tier)
125
+ } from '@ingissa/navcore-core/core';
126
+
127
+ // OSRM
128
+ const osrm = new OSRMDirectionsProvider({ baseUrl: 'http://router.project-osrm.org' });
129
+ const route = await osrm.getRoute([[lng1, lat1], [lng2, lat2]]);
130
+
131
+ // Valhalla
132
+ const valhalla = new ValhallaDirectionsProvider({ baseUrl: 'https://valhalla1.openstreetmap.de' });
133
+ const route = await valhalla.getRoute([[lng1, lat1], [lng2, lat2]], { costing: 'auto' });
134
+ ```
135
+
136
+ ---
137
+
138
+ ## Renderer Adapters (separate packages)
139
+
140
+ ```bash
141
+ npm install @ingissa/navcore-maplibre # Web + MapLibre GL
142
+ npm install @ingissa/navcore-leaflet # Web + Leaflet.js
143
+ npm install @ingissa/navcore-google-maps # Google Maps JS API
144
+ npm install @ingissa/navcore-mapbox # React Native + @rnmapbox/maps
145
+ npm install @ingissa/navcore-react-native # React Native bindings
146
+ npm install @ingissa/navcore-headless # Node.js / CI testing
147
+ ```
148
+
149
+ > ⚠️ Renderer adapter packages require a commercial license. [Get yours at Not yet available]()
150
+
151
+ ---
152
+
153
+ ## Feature Reference
154
+
155
+ ### CORE — free, MIT
156
+
157
+ | Feature | API |
158
+ |---|---|
159
+ | GPS smoothing | `KalmanFilter2D` |
160
+ | Route snapping | `RouteSnapper` |
161
+ | Haversine & bearing | `haversineDistance`, `getBearingBetweenPoints` |
162
+ | Cumulative distances | `buildForwardCumulativeDistances` |
163
+ | Route builder | `CustomRouteBuilder` |
164
+ | Directions — OSRM | `OSRMDirectionsProvider` |
165
+ | Directions — Valhalla | `ValhallaDirectionsProvider` |
166
+ | Directions — ORS | `OpenRouteServiceProvider` |
167
+ | Renderer interface | `MapRendererAdapter` |
168
+ | License verification | `LicenseManager` |
169
+
170
+ ### PRO — commercial license required
171
+
172
+ | Feature | API | FEATURES key |
173
+ |---|---|---|
174
+ | Advanced snapping (bearing + inertia) | `RouteSnapper` options | `ADVANCED_SNAPPING` |
175
+ | Parallel road resolver | `ParallelRoadResolver` | `PARALLEL_ROAD_RESOLUTION` |
176
+ | Dead reckoning | `DeadReckoningEngine` | `DEAD_RECKONING` |
177
+ | Compound deviation detection | `DeviationDetector` | `COMPOUND_DEVIATION` |
178
+ | Advanced bearing engine | `BearingEngine` | `ADVANCED_BEARING` |
179
+ | Progress anti-regression | `ProgressTracker` | `PROGRESS_TRACKING` |
180
+ | Route offset predictor | `RouteOffsetPredictor` | `ROUTE_OFFSET` |
181
+ | Navigation lifecycle FSM | `NavigationFSM` | — |
182
+ | Off-route FSM | `OffRouteFSM` | — |
183
+ | Instruction editor | `InstructionEditor` | — |
184
+ | Voice trigger engine | `VoiceTriggerEngine` | `VOICE_TRIGGERS` |
185
+ | ETA engine (offline) | `ETAEngine` | `ETA` |
186
+ | Geofencing engine | `GeofencingEngine` | `GEOFENCING` |
187
+
188
+ ---
189
+
190
+ ## License Gate Behaviour
191
+
192
+ | Condition | Result |
193
+ |---|---|
194
+ | No `licenseKey` | Core features work; Pro features blocked with `console.warn` |
195
+ | Valid Pro key | All features unlocked; `🔑 [NavCore] License validation complete. Plan: pro` logged |
196
+ | `isDev: true` | All features bypassed (development mode) |
197
+ | Expired / tampered key | Pro features blocked; status: `EXPIRED` / `INVALID` |
198
+
199
+ License keys are verified **offline** using ECDSA P-256 — no network calls, no tracking.
200
+
201
+ ---
202
+
203
+ ## NavCore Engine API
204
+
205
+ ```typescript
206
+ const engine = new NavCore(options?: NavCoreOptions);
207
+
208
+ // Setup
209
+ engine.setRoute(coords: Coordinate[], instructions?: NavInstruction[]);
210
+ engine.startNavigation();
211
+
212
+ // GPS update (returns state snapshot)
213
+ const state: NavCoreState = engine.update(gpsUpdate: NavGpsUpdate);
214
+
215
+ // Render tick (call at ~60fps between GPS updates)
216
+ engine.tick(nowMs: number);
217
+
218
+ // Events
219
+ engine.on('instruction', (instr: NavInstruction) => void);
220
+ engine.on('deviation', ({ anchorIndex, ghostCoord }) => void);
221
+ engine.on('arrival', () => void);
222
+ engine.on('routeLoaded', ({ routeLength }) => void);
223
+ engine.off(event, handler);
224
+
225
+ // State & introspection
226
+ engine.getState(): NavCoreState;
227
+ engine.getLicenseManager(): LicenseManager;
228
+ engine.getRoute(): ReadonlyArray<Coordinate>;
229
+ engine.destroy();
230
+ ```
231
+
232
+ ### NavCoreState
233
+
234
+ ```typescript
235
+ interface NavCoreState {
236
+ snappedCoord: Coordinate | null; // GPS-snapped position on route
237
+ rawGpsCoord: Coordinate | null; // Raw GPS position
238
+ bearing: number; // Smoothed heading (degrees)
239
+ routeIndex: number; // Current segment index
240
+ distanceToRoute: number; // Distance from route (metres)
241
+ corridor: number; // Dynamic off-route threshold (metres)
242
+ isOffRoute: boolean; // True when deviation confirmed
243
+ hasArrived: boolean;
244
+ distanceToDestination: number | null; // Remaining distance (metres)
245
+ distanceToNextInstruction: number | null; // Distance to next turn
246
+ nextInstructionIndex: number | null;
247
+ currentSpeed: number; // m/s
248
+ isGpsStale: boolean;
249
+ gpsAgeMs: number;
250
+ licenseStatus: string; // 'VALID' | 'INVALID' | 'EXPIRED' | 'MISSING'
251
+ }
252
+ ```
253
+
254
+ ---
255
+
256
+ ## Documentation
257
+
258
+ | Guide | Link |
259
+ |---|---|
260
+ | Architecture | [docs/architecture.md](./docs/architecture.md) |
261
+ | Getting Started | [docs/getting-started.md](./docs/getting-started.md) |
262
+ | Directions Providers | [docs/directions-providers.md](./docs/directions-providers.md) |
263
+ | Renderer Adapters | [docs/renderer-adapters.md](./docs/renderer-adapters.md) |
264
+ | API Reference | [docs/api-reference.md](./docs/api-reference.md) |
265
+ | Custom Routes | [docs/custom-routes.md](./docs/custom-routes.md) |
266
+ | Instructions & Voice | [docs/instructions.md](./docs/instructions.md) |
267
+ | Pro Features | [docs/p3-features.md](./docs/p3-features.md) |
268
+ | Testing Guide | [docs/testing.md](./docs/testing.md) |
269
+
270
+ ---
271
+
272
+ ## License
273
+
274
+ - **Core engine** (`@ingissa/navcore-core`): [MIT](./LICENSE) for open-source projects
275
+ - **Pro features & adapters**: Commercial license required — [Not yet available]()
276
+
277
+ ---
278
+
279
+ © Ingissa · [8abnav@gmail.com](mailto:8abnav@gmail.com)
package/dist/core.js CHANGED
@@ -4,5 +4,4 @@ ${this.waypoints.map(e=>` <wpt lat="${e.coord[1]}" lon="${e.coord[0]}">`+(e.o
4
4
  <name>${e.options.name}</name>`:"")+`
5
5
  </wpt>`).join(`
6
6
  `)}
7
- </gpx>`}fromGeoJSON(t){if(this.waypoints=[],t.type==="FeatureCollection"&&t.features){for(let e of t.features)if(e.geometry.type==="Point"){let n=e.geometry.coordinates;this.addWaypoint(n);}else if(e.geometry.type==="LineString"){let n=e.geometry.coordinates;for(let r of n)this.addWaypoint(r);}}}toGeoJSON(){let t=this.waypoints.map((n,r)=>({type:"Feature",geometry:{type:"Point",coordinates:n.coord},properties:{id:n.id,order:r,name:n.options?.name??null,silent:n.options?.silent??false}})),e={type:"Feature",geometry:{type:"LineString",coordinates:this.waypoints.map(n=>n.coord)},properties:{type:"route"}};return {type:"FeatureCollection",features:[...t,e]}}clear(){this.waypoints=[];}};var O={GPS_SMOOTHING:"gps_smoothing",BASIC_SNAPPING:"basic_snapping",ADVANCED_SNAPPING:"advanced_snapping",DEAD_RECKONING:"dead_reckoning",ROUTE_OFFSET:"route_offset",PROGRESS_TRACKING:"progress_tracking",ADVANCED_BEARING:"advanced_bearing",COMPOUND_DEVIATION:"compound_deviation",PARALLEL_ROAD_RESOLUTION:"parallel_road_resolution",U_TURN_PROTECTION:"u_turn_protection",VOICE_TRIGGERS:"voice_triggers",ETA:"eta",GEOFENCING:"geofencing",NAV_AI:"nav_ai"};var g=class g{constructor(t,e){this.status="MISSING";this.payload=null;this.enabledFeatures=new Set;this.warnedFeatures=new Set;this.isDev=false;this.isDev=e??false,t&&this.validate(t);}async validate(t){try{if(this.status==="VALID"&&this.payload?.bid==="dev.bypass")return "VALID";let[e,n]=t.split(".");if(!e||!n)return this.status="INVALID",this.status;let r=atob(e);if(this.payload=JSON.parse(r),!this.payload||this.payload.exp<Date.now())return this.status="EXPIRED",this.status;if(!await this.verifySignature(e,n))return this.status="INVALID",this.status;this.status="VALID",this.extractFeatures(),console.log(`\u{1F511} [NavCore] License validation complete. Plan: ${this.isProEnabled()?"pro":"core"}`);}catch{this.status="INVALID";}return this.status}extractFeatures(){if(this.enabledFeatures.clear(),!!this.payload){if(Array.isArray(this.payload.f))for(let t of this.payload.f)this.enabledFeatures.add(t);this.payload.t==="pro"&&this.enabledFeatures.add("*");}}hasFeature(t){return this.isDev?true:this.status!=="VALID"?false:this.enabledFeatures.has("*")?true:this.enabledFeatures.has(t)}warnOnce(t){this.warnedFeatures.has(t)||(console.warn(`[NavCore] Feature '${t}' requires a Pro license \u2192 upgrade at https://navcore.dev/pricing`),this.warnedFeatures.add(t));}isProEnabled(){return this.hasFeature("*")}isAIEnabled(){return this.hasFeature(O.NAV_AI)}getStatus(){return this.status}getPayload(){return this.payload}getEnabledFeatures(){return Array.from(this.enabledFeatures)}async verifySignature(t,e){try{if(typeof crypto>"u"||!crypto.subtle)return !1;let r=new TextEncoder().encode(t),s=Uint8Array.from(atob(e),a=>a.charCodeAt(0)),l=Uint8Array.from(atob(g.PUBLIC_KEY_SPKI),a=>a.charCodeAt(0)),p=await crypto.subtle.importKey("spki",l,{name:"ECDSA",namedCurve:"P-256"},!0,["verify"]);return await crypto.subtle.verify({name:"ECDSA",hash:{name:"SHA-256"}},p,s,r)}catch{return false}}};g.PUBLIC_KEY_SPKI="MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEk6onJmJkWtRFusiSS69dcdONX4xL2Snm4AZO+dYjSynfJiL4x7trEy80MImLftknBWT/qI0z7ox4Q7VNL3te6A==";var I=g;
8
- exports.Constants=W;exports.CustomRouteBuilder=v;exports.FEATURES=O;exports.KalmanFilter2D=S;exports.LicenseManager=I;exports.OSRMDirectionsProvider=D;exports.OpenRouteServiceProvider=M;exports.RouteSnapper=E;exports.ValhallaDirectionsProvider=_;exports.buildForwardCumulativeDistances=z;exports.buildReverseCumulativeDistances=Y;exports.destinationPoint=X;exports.geoGetBearing=j;exports.getBearingBetweenPoints=x;exports.haversineDistance=f;exports.interpolatePoint=q;exports.kalmanUpdate2D=L;exports.normalizeAngle=P;exports.projectPointOnSegment=A;
7
+ </gpx>`}fromGeoJSON(t){if(this.waypoints=[],t.type==="FeatureCollection"&&t.features){for(let e of t.features)if(e.geometry.type==="Point"){let n=e.geometry.coordinates;this.addWaypoint(n);}else if(e.geometry.type==="LineString"){let n=e.geometry.coordinates;for(let r of n)this.addWaypoint(r);}}}toGeoJSON(){let t=this.waypoints.map((n,r)=>({type:"Feature",geometry:{type:"Point",coordinates:n.coord},properties:{id:n.id,order:r,name:n.options?.name??null,silent:n.options?.silent??false}})),e={type:"Feature",geometry:{type:"LineString",coordinates:this.waypoints.map(n=>n.coord)},properties:{type:"route"}};return {type:"FeatureCollection",features:[...t,e]}}clear(){this.waypoints=[];}};var O={GPS_SMOOTHING:"gps_smoothing",BASIC_SNAPPING:"basic_snapping",ADVANCED_SNAPPING:"advanced_snapping",DEAD_RECKONING:"dead_reckoning",ROUTE_OFFSET:"route_offset",PROGRESS_TRACKING:"progress_tracking",ADVANCED_BEARING:"advanced_bearing",COMPOUND_DEVIATION:"compound_deviation",PARALLEL_ROAD_RESOLUTION:"parallel_road_resolution",U_TURN_PROTECTION:"u_turn_protection",VOICE_TRIGGERS:"voice_triggers",ETA:"eta",GEOFENCING:"geofencing",NAV_AI:"nav_ai"};var g=class g{constructor(t,e){this.status="MISSING";this.payload=null;this.enabledFeatures=new Set;this.warnedFeatures=new Set;this.isDev=false;this.isDev=e??false,t&&this.validate(t);}async validate(t){try{if(this.status==="VALID"&&this.payload?.bid==="dev.bypass")return "VALID";let[e,n]=t.split(".");if(!e||!n)return this.status="INVALID",this.status;let r=atob(e);if(this.payload=JSON.parse(r),!this.payload||this.payload.exp<Date.now())return this.status="EXPIRED",this.status;if(!await this.verifySignature(e,n))return this.status="INVALID",this.status;this.status="VALID",this.extractFeatures(),console.log(`\u{1F511} [NavCore] License validation complete. Plan: ${this.isProEnabled()?"pro":"core"}`);}catch{this.status="INVALID";}return this.status}extractFeatures(){if(this.enabledFeatures.clear(),!!this.payload){if(Array.isArray(this.payload.f))for(let t of this.payload.f)this.enabledFeatures.add(t);this.payload.t==="pro"&&this.enabledFeatures.add("*");}}hasFeature(t){return this.isDev?true:this.status!=="VALID"?false:this.enabledFeatures.has("*")?true:this.enabledFeatures.has(t)}warnOnce(t){this.warnedFeatures.has(t)||(console.warn(`[NavCore] Feature '${t}' requires a Pro license`),this.warnedFeatures.add(t));}isProEnabled(){return this.hasFeature("*")}isAIEnabled(){return this.hasFeature(O.NAV_AI)}getStatus(){return this.status}getPayload(){return this.payload}getEnabledFeatures(){return Array.from(this.enabledFeatures)}async verifySignature(t,e){try{if(typeof crypto>"u"||!crypto.subtle)return !1;let r=new TextEncoder().encode(t),s=Uint8Array.from(atob(e),a=>a.charCodeAt(0)),l=Uint8Array.from(atob(g.PUBLIC_KEY_SPKI),a=>a.charCodeAt(0)),p=await crypto.subtle.importKey("spki",l,{name:"ECDSA",namedCurve:"P-256"},!0,["verify"]);return await crypto.subtle.verify({name:"ECDSA",hash:{name:"SHA-256"}},p,s,r)}catch{return false}}};g.PUBLIC_KEY_SPKI="MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEk6onJmJkWtRFusiSS69dcdONX4xL2Snm4AZO+dYjSynfJiL4x7trEy80MImLftknBWT/qI0z7ox4Q7VNL3te6A==";var I=g;exports.Constants=W;exports.CustomRouteBuilder=v;exports.FEATURES=O;exports.KalmanFilter2D=S;exports.LicenseManager=I;exports.OSRMDirectionsProvider=D;exports.OpenRouteServiceProvider=M;exports.RouteSnapper=E;exports.ValhallaDirectionsProvider=_;exports.buildForwardCumulativeDistances=z;exports.buildReverseCumulativeDistances=Y;exports.destinationPoint=X;exports.geoGetBearing=j;exports.getBearingBetweenPoints=x;exports.haversineDistance=f;exports.interpolatePoint=q;exports.kalmanUpdate2D=L;exports.normalizeAngle=P;exports.projectPointOnSegment=A;
package/dist/core.mjs CHANGED
@@ -4,5 +4,4 @@ ${this.waypoints.map(e=>` <wpt lat="${e.coord[1]}" lon="${e.coord[0]}">`+(e.o
4
4
  <name>${e.options.name}</name>`:"")+`
5
5
  </wpt>`).join(`
6
6
  `)}
7
- </gpx>`}fromGeoJSON(t){if(this.waypoints=[],t.type==="FeatureCollection"&&t.features){for(let e of t.features)if(e.geometry.type==="Point"){let n=e.geometry.coordinates;this.addWaypoint(n);}else if(e.geometry.type==="LineString"){let n=e.geometry.coordinates;for(let r of n)this.addWaypoint(r);}}}toGeoJSON(){let t=this.waypoints.map((n,r)=>({type:"Feature",geometry:{type:"Point",coordinates:n.coord},properties:{id:n.id,order:r,name:n.options?.name??null,silent:n.options?.silent??false}})),e={type:"Feature",geometry:{type:"LineString",coordinates:this.waypoints.map(n=>n.coord)},properties:{type:"route"}};return {type:"FeatureCollection",features:[...t,e]}}clear(){this.waypoints=[];}};var O={GPS_SMOOTHING:"gps_smoothing",BASIC_SNAPPING:"basic_snapping",ADVANCED_SNAPPING:"advanced_snapping",DEAD_RECKONING:"dead_reckoning",ROUTE_OFFSET:"route_offset",PROGRESS_TRACKING:"progress_tracking",ADVANCED_BEARING:"advanced_bearing",COMPOUND_DEVIATION:"compound_deviation",PARALLEL_ROAD_RESOLUTION:"parallel_road_resolution",U_TURN_PROTECTION:"u_turn_protection",VOICE_TRIGGERS:"voice_triggers",ETA:"eta",GEOFENCING:"geofencing",NAV_AI:"nav_ai"};var g=class g{constructor(t,e){this.status="MISSING";this.payload=null;this.enabledFeatures=new Set;this.warnedFeatures=new Set;this.isDev=false;this.isDev=e??false,t&&this.validate(t);}async validate(t){try{if(this.status==="VALID"&&this.payload?.bid==="dev.bypass")return "VALID";let[e,n]=t.split(".");if(!e||!n)return this.status="INVALID",this.status;let r=atob(e);if(this.payload=JSON.parse(r),!this.payload||this.payload.exp<Date.now())return this.status="EXPIRED",this.status;if(!await this.verifySignature(e,n))return this.status="INVALID",this.status;this.status="VALID",this.extractFeatures(),console.log(`\u{1F511} [NavCore] License validation complete. Plan: ${this.isProEnabled()?"pro":"core"}`);}catch{this.status="INVALID";}return this.status}extractFeatures(){if(this.enabledFeatures.clear(),!!this.payload){if(Array.isArray(this.payload.f))for(let t of this.payload.f)this.enabledFeatures.add(t);this.payload.t==="pro"&&this.enabledFeatures.add("*");}}hasFeature(t){return this.isDev?true:this.status!=="VALID"?false:this.enabledFeatures.has("*")?true:this.enabledFeatures.has(t)}warnOnce(t){this.warnedFeatures.has(t)||(console.warn(`[NavCore] Feature '${t}' requires a Pro license \u2192 upgrade at https://navcore.dev/pricing`),this.warnedFeatures.add(t));}isProEnabled(){return this.hasFeature("*")}isAIEnabled(){return this.hasFeature(O.NAV_AI)}getStatus(){return this.status}getPayload(){return this.payload}getEnabledFeatures(){return Array.from(this.enabledFeatures)}async verifySignature(t,e){try{if(typeof crypto>"u"||!crypto.subtle)return !1;let r=new TextEncoder().encode(t),s=Uint8Array.from(atob(e),a=>a.charCodeAt(0)),l=Uint8Array.from(atob(g.PUBLIC_KEY_SPKI),a=>a.charCodeAt(0)),p=await crypto.subtle.importKey("spki",l,{name:"ECDSA",namedCurve:"P-256"},!0,["verify"]);return await crypto.subtle.verify({name:"ECDSA",hash:{name:"SHA-256"}},p,s,r)}catch{return false}}};g.PUBLIC_KEY_SPKI="MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEk6onJmJkWtRFusiSS69dcdONX4xL2Snm4AZO+dYjSynfJiL4x7trEy80MImLftknBWT/qI0z7ox4Q7VNL3te6A==";var I=g;
8
- export{W as Constants,v as CustomRouteBuilder,O as FEATURES,S as KalmanFilter2D,I as LicenseManager,D as OSRMDirectionsProvider,M as OpenRouteServiceProvider,E as RouteSnapper,_ as ValhallaDirectionsProvider,z as buildForwardCumulativeDistances,Y as buildReverseCumulativeDistances,X as destinationPoint,j as geoGetBearing,x as getBearingBetweenPoints,f as haversineDistance,q as interpolatePoint,L as kalmanUpdate2D,P as normalizeAngle,A as projectPointOnSegment};
7
+ </gpx>`}fromGeoJSON(t){if(this.waypoints=[],t.type==="FeatureCollection"&&t.features){for(let e of t.features)if(e.geometry.type==="Point"){let n=e.geometry.coordinates;this.addWaypoint(n);}else if(e.geometry.type==="LineString"){let n=e.geometry.coordinates;for(let r of n)this.addWaypoint(r);}}}toGeoJSON(){let t=this.waypoints.map((n,r)=>({type:"Feature",geometry:{type:"Point",coordinates:n.coord},properties:{id:n.id,order:r,name:n.options?.name??null,silent:n.options?.silent??false}})),e={type:"Feature",geometry:{type:"LineString",coordinates:this.waypoints.map(n=>n.coord)},properties:{type:"route"}};return {type:"FeatureCollection",features:[...t,e]}}clear(){this.waypoints=[];}};var O={GPS_SMOOTHING:"gps_smoothing",BASIC_SNAPPING:"basic_snapping",ADVANCED_SNAPPING:"advanced_snapping",DEAD_RECKONING:"dead_reckoning",ROUTE_OFFSET:"route_offset",PROGRESS_TRACKING:"progress_tracking",ADVANCED_BEARING:"advanced_bearing",COMPOUND_DEVIATION:"compound_deviation",PARALLEL_ROAD_RESOLUTION:"parallel_road_resolution",U_TURN_PROTECTION:"u_turn_protection",VOICE_TRIGGERS:"voice_triggers",ETA:"eta",GEOFENCING:"geofencing",NAV_AI:"nav_ai"};var g=class g{constructor(t,e){this.status="MISSING";this.payload=null;this.enabledFeatures=new Set;this.warnedFeatures=new Set;this.isDev=false;this.isDev=e??false,t&&this.validate(t);}async validate(t){try{if(this.status==="VALID"&&this.payload?.bid==="dev.bypass")return "VALID";let[e,n]=t.split(".");if(!e||!n)return this.status="INVALID",this.status;let r=atob(e);if(this.payload=JSON.parse(r),!this.payload||this.payload.exp<Date.now())return this.status="EXPIRED",this.status;if(!await this.verifySignature(e,n))return this.status="INVALID",this.status;this.status="VALID",this.extractFeatures(),console.log(`\u{1F511} [NavCore] License validation complete. Plan: ${this.isProEnabled()?"pro":"core"}`);}catch{this.status="INVALID";}return this.status}extractFeatures(){if(this.enabledFeatures.clear(),!!this.payload){if(Array.isArray(this.payload.f))for(let t of this.payload.f)this.enabledFeatures.add(t);this.payload.t==="pro"&&this.enabledFeatures.add("*");}}hasFeature(t){return this.isDev?true:this.status!=="VALID"?false:this.enabledFeatures.has("*")?true:this.enabledFeatures.has(t)}warnOnce(t){this.warnedFeatures.has(t)||(console.warn(`[NavCore] Feature '${t}' requires a Pro license`),this.warnedFeatures.add(t));}isProEnabled(){return this.hasFeature("*")}isAIEnabled(){return this.hasFeature(O.NAV_AI)}getStatus(){return this.status}getPayload(){return this.payload}getEnabledFeatures(){return Array.from(this.enabledFeatures)}async verifySignature(t,e){try{if(typeof crypto>"u"||!crypto.subtle)return !1;let r=new TextEncoder().encode(t),s=Uint8Array.from(atob(e),a=>a.charCodeAt(0)),l=Uint8Array.from(atob(g.PUBLIC_KEY_SPKI),a=>a.charCodeAt(0)),p=await crypto.subtle.importKey("spki",l,{name:"ECDSA",namedCurve:"P-256"},!0,["verify"]);return await crypto.subtle.verify({name:"ECDSA",hash:{name:"SHA-256"}},p,s,r)}catch{return false}}};g.PUBLIC_KEY_SPKI="MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEk6onJmJkWtRFusiSS69dcdONX4xL2Snm4AZO+dYjSynfJiL4x7trEy80MImLftknBWT/qI0z7ox4Q7VNL3te6A==";var I=g;export{W as Constants,v as CustomRouteBuilder,O as FEATURES,S as KalmanFilter2D,I as LicenseManager,D as OSRMDirectionsProvider,M as OpenRouteServiceProvider,E as RouteSnapper,_ as ValhallaDirectionsProvider,z as buildForwardCumulativeDistances,Y as buildReverseCumulativeDistances,X as destinationPoint,j as geoGetBearing,x as getBearingBetweenPoints,f as haversineDistance,q as interpolatePoint,L as kalmanUpdate2D,P as normalizeAngle,A as projectPointOnSegment};
package/dist/index.js CHANGED
@@ -4,5 +4,5 @@ ${this.waypoints.map(t=>` <wpt lat="${t.coord[1]}" lon="${t.coord[0]}">`+(t.o
4
4
  <name>${t.options.name}</name>`:"")+`
5
5
  </wpt>`).join(`
6
6
  `)}
7
- </gpx>`}fromGeoJSON(e){if(this.waypoints=[],e.type==="FeatureCollection"&&e.features){for(let t of e.features)if(t.geometry.type==="Point"){let n=t.geometry.coordinates;this.addWaypoint(n);}else if(t.geometry.type==="LineString"){let n=t.geometry.coordinates;for(let i of n)this.addWaypoint(i);}}}toGeoJSON(){let e=this.waypoints.map((n,i)=>({type:"Feature",geometry:{type:"Point",coordinates:n.coord},properties:{id:n.id,order:i,name:n.options?.name??null,silent:n.options?.silent??false}})),t={type:"Feature",geometry:{type:"LineString",coordinates:this.waypoints.map(n=>n.coord)},properties:{type:"route"}};return {type:"FeatureCollection",features:[...e,t]}}clear(){this.waypoints=[];}};var ae=class{constructor(e){this.config={baseUrl:e.baseUrl.replace(/\/$/,""),profile:e.profile??"driving",maxWaypointsPerChunk:e.maxWaypointsPerChunk??100};}async getRoute(e,t){if(e.length<2)throw new Error("[OSRM] At least 2 waypoints required");let n=t?.profile??this.config.profile;return e.length<=this.config.maxWaypointsPerChunk?this.fetchRoute(e,n,t):this.fetchChunked(e,n,t)}async fetchRoute(e,t,n){let i=e.map(d=>`${d[0]},${d[1]}`).join(";"),r=n?.steps!==false?"true":"false",o=`${this.config.baseUrl}/route/v1/${t}/${i}?overview=full&geometries=geojson&steps=${r}&annotations=false`,c=await fetch(o);if(!c.ok)throw new Error(`[OSRM] HTTP ${c.status}: ${c.statusText}`);let a=await c.json();if(a.code!=="Ok"||!a.routes?.length)throw new Error(`[OSRM] No route found: ${a.code}`);let u=a.routes[0];return {geometry:u.geometry.coordinates,distance:u.distance,duration:u.duration,legs:(u.legs??[]).map(d=>({distance:d.distance,duration:d.duration,steps:(d.steps??[]).map(l=>({distance:l.distance,duration:l.duration,geometry:l.geometry?.coordinates??[],maneuver:{type:l.maneuver?.type??"unknown",modifier:l.maneuver?.modifier,location:l.maneuver?.location??[0,0],bearingBefore:l.maneuver?.bearing_before??0,bearingAfter:l.maneuver?.bearing_after??0,instruction:l.name??void 0},name:l.name}))})),raw:a}}async fetchChunked(e,t,n){let i=this.config.maxWaypointsPerChunk,r=[];for(let l=0;l<e.length;l+=i-1){let p=e.slice(l,l+i);if(p.length<2||(r.push(p),l+i>=e.length))break}let o=await Promise.all(r.map(l=>this.fetchRoute(l,t,n))),c=[],a=0,u=0,d=[];for(let l=0;l<o.length;l++){let p=o[l];c.push(...l===0?p.geometry:p.geometry.slice(1)),a+=p.distance,u+=p.duration,d.push(...p.legs);}return {geometry:c,distance:a,duration:u,legs:d,raw:o.map(l=>l.raw)}}};var ce=class{constructor(e){this.config={baseUrl:e.baseUrl.replace(/\/$/,""),costing:e.costing??"auto",apiKey:e.apiKey??"",maxWaypointsPerChunk:e.maxWaypointsPerChunk??50};}async getRoute(e,t){if(e.length<2)throw new Error("[Valhalla] At least 2 waypoints required");let n=this.profileToCosting(t?.profile);return e.length<=this.config.maxWaypointsPerChunk?this.fetchRoute(e,n,t):this.fetchChunked(e,n,t)}profileToCosting(e){switch(e){case "cycling":return "bicycle";case "walking":return "pedestrian";default:return this.config.costing}}async fetchRoute(e,t,n){let i=e.map(p=>({lon:p[0],lat:p[1],type:"break"})),r=JSON.stringify({locations:i,costing:t,directions_options:{units:"kilometers"}}),o={"Content-Type":"application/json"};this.config.apiKey&&(o.api_key=this.config.apiKey);let c=await fetch(`${this.config.baseUrl}/route`,{method:"POST",headers:o,body:r});if(!c.ok){let p=await c.text();throw new Error(`[Valhalla] HTTP ${c.status}: ${p}`)}let a=await c.json(),u=this.decodePolyline(a.trip?.legs?.[0]?.shape??""),d=a.trip?.summary??{},l=(a.trip?.legs??[]).map(p=>({distance:(p.summary?.length??0)*1e3,duration:p.summary?.time??0,steps:(p.maneuvers??[]).map(g=>({distance:(g.length??0)*1e3,duration:g.time??0,geometry:[],maneuver:{type:g.type?.toString()??"unknown",modifier:void 0,location:e[0],bearingBefore:g.begin_heading??0,bearingAfter:g.end_heading??0,instruction:g.instruction},name:g.street_names?.[0]}))}));return {geometry:u,distance:(d.length??0)*1e3,duration:d.time??0,legs:l,raw:a}}decodePolyline(e,t=6){let n=Math.pow(10,t),i=[],r=0,o=0,c=0;for(;r<e.length;){let a=0,u=0,d;do d=e.charCodeAt(r++)-63,u|=(d&31)<<a,a+=5;while(d>=32);let l=(u&1)!==0?~(u>>1):u>>1;o+=l,a=0,u=0;do d=e.charCodeAt(r++)-63,u|=(d&31)<<a,a+=5;while(d>=32);let p=(u&1)!==0?~(u>>1):u>>1;c+=p,i.push([c/n,o/n]);}return i}async fetchChunked(e,t,n){let i=this.config.maxWaypointsPerChunk,r=[];for(let l=0;l<e.length;l+=i-1){let p=e.slice(l,l+i);if(p.length<2||(r.push(p),l+i>=e.length))break}let o=await Promise.all(r.map(l=>this.fetchRoute(l,t,n))),c=[],a=0,u=0,d=[];for(let l=0;l<o.length;l++){let p=o[l];c.push(...l===0?p.geometry:p.geometry.slice(1)),a+=p.distance,u+=p.duration,d.push(...p.legs);}return {geometry:c,distance:a,duration:u,legs:d,raw:o.map(l=>l.raw)}}};var Ze="https://api.openrouteservice.org/v2/directions";var le=class{constructor(e){this.config={apiKey:e.apiKey,profile:e.profile??"driving-car",baseUrl:e.baseUrl??Ze,maxWaypointsPerChunk:e.maxWaypointsPerChunk??50};}async getRoute(e,t){if(e.length<2)throw new Error("[ORS] At least 2 waypoints required");let n=this.profileFromOptions(t);return e.length<=this.config.maxWaypointsPerChunk?this.fetchRoute(e,n,t):this.fetchChunked(e,n,t)}profileFromOptions(e){switch(e?.profile){case "cycling":return "cycling-regular";case "walking":return "foot-walking";default:return this.config.profile}}async fetchRoute(e,t,n){let i=JSON.stringify({coordinates:e,instructions:n?.steps!==false,language:n?.language??"en"}),r=await fetch(`${this.config.baseUrl}/${t}/geojson`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:this.config.apiKey},body:i});if(!r.ok){let l=await r.text();throw new Error(`[ORS] HTTP ${r.status}: ${l}`)}let o=await r.json(),c=o.features?.[0];if(!c)throw new Error("[ORS] No route returned");let a=c.properties?.summary??{},d=(c.properties?.segments??[]).map(l=>({distance:l.distance??0,duration:l.duration??0,steps:(l.steps??[]).map(p=>({distance:p.distance??0,duration:p.duration??0,geometry:[],maneuver:{type:p.type?.toString()??"unknown",modifier:void 0,location:e[0],bearingBefore:0,bearingAfter:0,instruction:p.instruction},name:p.name}))}));return {geometry:c.geometry?.coordinates??[],distance:a.distance??0,duration:a.duration??0,legs:d,raw:o}}async fetchChunked(e,t,n){let i=this.config.maxWaypointsPerChunk,r=[];for(let l=0;l<e.length;l+=i-1){let p=e.slice(l,l+i);if(p.length<2||(r.push(p),l+i>=e.length))break}let o=await Promise.all(r.map(l=>this.fetchRoute(l,t,n))),c=[],a=0,u=0,d=[];for(let l=0;l<o.length;l++){let p=o[l];c.push(...l===0?p.geometry:p.geometry.slice(1)),a+=p.distance,u+=p.duration,d.push(...p.legs);}return {geometry:c,distance:a,duration:u,legs:d,raw:o.map(l=>l.raw)}}};var et={earlyTriggerMeters:120,lateTriggerMeters:20,reannounceMeters:300},ue=class{constructor(e){this.instructions=[];this.triggeredIds=new Set;this.reannouncedIds=new Set;this.options={...et,...e};}setInstructions(e){this.instructions=[...e],this.triggeredIds.clear(),this.reannouncedIds.clear();}update(e){let{distanceToNextInstruction:t,nextInstructionIndex:n}=e;if(n===null||t===null||n>=this.instructions.length)return null;let i=this.instructions[n],r=this.instrId(n),o=t;if(this.triggeredIds.has(r))return this.options.reannounceMeters>0&&!this.reannouncedIds.has(r)&&o<=this.options.reannounceMeters&&o>this.options.earlyTriggerMeters?(this.reannouncedIds.add(r),this.makeCue(i,o,"low")):null;if(o>this.options.earlyTriggerMeters)return null;if(o<-this.options.lateTriggerMeters)return this.triggeredIds.add(r),null;this.triggeredIds.add(r);let c=o<30?"urgent":o<60?"high":"normal";return this.makeCue(i,o,c)}onInstruction(e,t){let n=t.distanceToNextInstruction??0;return this.makeCue(e,n,"high")}reset(){this.triggeredIds.clear(),this.reannouncedIds.clear();}instrId(e){let t=this.instructions[e];return `${e}_${t?.geometryIndex??0}`}makeCue(e,t,n){return {instruction:e,text:e.text??e.instruction??"",distanceMetres:Math.max(0,t),priority:n,triggerId:`${e.geometryIndex}_${t.toFixed(0)}`}}};var tt={speedWindowSize:6,minimumSpeedMs:.5,fallbackSpeedMs:8.33},de=class{constructor(e){this.speedSamples=[];this.options={...tt,...e},this.lastReliableSpeed=this.options.fallbackSpeedMs;}update(e){let t=e.distanceToDestination??0,n=e.currentSpeed??0;this.speedSamples.push(n),this.speedSamples.length>this.options.speedWindowSize&&this.speedSamples.shift();let i=this.rollingAverage();i>=this.options.minimumSpeedMs&&(this.lastReliableSpeed=i);let r=i>=this.options.minimumSpeedMs?i:this.lastReliableSpeed,o=this.speedSamples.length>=Math.ceil(this.options.speedWindowSize/2)&&r>0&&t>0,c=r>0?t/r:0;return {etaSeconds:Math.round(c),etaMs:Date.now()+c*1e3,distanceRemainingM:t,averageSpeedMs:i,isReliable:o}}reset(){this.speedSamples=[],this.lastReliableSpeed=this.options.fallbackSpeedMs;}rollingAverage(){return this.speedSamples.length===0?0:this.speedSamples.reduce((t,n)=>t+n,0)/this.speedSamples.length}};var nt={dwellThresholdMs:1e4},pe=class{constructor(e){this.fences=new Map;this.states=new Map;this.options={...nt,...e};}addCircle(e,t,n,i){return this.fences.set(e,{kind:"circle",id:e,center:t,radiusM:n,meta:i}),this.states.set(e,{inside:false,enteredAtMs:null}),e}addPolygon(e,t,n){if(t.length<3)throw new Error("[Geofencing] Polygon requires at least 3 coordinates");return this.fences.set(e,{kind:"polygon",id:e,coords:t,meta:n}),this.states.set(e,{inside:false,enteredAtMs:null}),e}remove(e){this.fences.delete(e),this.states.delete(e);}clear(){this.fences.clear(),this.states.clear();}listIds(){return Array.from(this.fences.keys())}update(e){if(!e)return [];let t=[],n=Date.now();for(let[i,r]of this.fences){let o=this.states.get(i),c=this.contains(r,e);if(!o.inside&&c)o.inside=true,o.enteredAtMs=n,t.push({id:i,type:"enter",coord:e,meta:r.meta});else if(o.inside&&!c){o.inside=false;let a=o.enteredAtMs?n-o.enteredAtMs:0;o.enteredAtMs=null,t.push({id:i,type:"exit",coord:e,dwellMs:a,meta:r.meta});}else if(o.inside&&o.enteredAtMs!==null){let a=n-o.enteredAtMs;a>=this.options.dwellThresholdMs&&(t.push({id:i,type:"dwell",coord:e,dwellMs:a,meta:r.meta}),o.enteredAtMs=n);}}return t}isInside(e,t){let n=this.fences.get(e);return n?this.contains(n,t):false}contains(e,t){return e.kind==="circle"?m(t[1],t[0],e.center[1],e.center[0])<=e.radiusM:this.pointInPolygon(t,e.coords)}pointInPolygon(e,t){let[n,i]=e,r=false,o=t.length;for(let c=0,a=o-1;c<o;a=c++){let[u,d]=t[c],[l,p]=t[a];d>i!=p>i&&n<(l-u)*(i-d)/(p-d)+u&&(r=!r);}return r}};var E={GPS_SMOOTHING:"gps_smoothing",BASIC_SNAPPING:"basic_snapping",ADVANCED_SNAPPING:"advanced_snapping",DEAD_RECKONING:"dead_reckoning",ROUTE_OFFSET:"route_offset",PROGRESS_TRACKING:"progress_tracking",ADVANCED_BEARING:"advanced_bearing",COMPOUND_DEVIATION:"compound_deviation",PARALLEL_ROAD_RESOLUTION:"parallel_road_resolution",U_TURN_PROTECTION:"u_turn_protection",VOICE_TRIGGERS:"voice_triggers",ETA:"eta",GEOFENCING:"geofencing",NAV_AI:"nav_ai"};var B=class B{constructor(e,t){this.status="MISSING";this.payload=null;this.enabledFeatures=new Set;this.warnedFeatures=new Set;this.isDev=false;this.isDev=t??false,e&&this.validate(e);}async validate(e){try{if(this.status==="VALID"&&this.payload?.bid==="dev.bypass")return "VALID";let[t,n]=e.split(".");if(!t||!n)return this.status="INVALID",this.status;let i=atob(t);if(this.payload=JSON.parse(i),!this.payload||this.payload.exp<Date.now())return this.status="EXPIRED",this.status;if(!await this.verifySignature(t,n))return this.status="INVALID",this.status;this.status="VALID",this.extractFeatures(),console.log(`\u{1F511} [NavCore] License validation complete. Plan: ${this.isProEnabled()?"pro":"core"}`);}catch{this.status="INVALID";}return this.status}extractFeatures(){if(this.enabledFeatures.clear(),!!this.payload){if(Array.isArray(this.payload.f))for(let e of this.payload.f)this.enabledFeatures.add(e);this.payload.t==="pro"&&this.enabledFeatures.add("*");}}hasFeature(e){return this.isDev?true:this.status!=="VALID"?false:this.enabledFeatures.has("*")?true:this.enabledFeatures.has(e)}warnOnce(e){this.warnedFeatures.has(e)||(console.warn(`[NavCore] Feature '${e}' requires a Pro license \u2192 upgrade at https://navcore.dev/pricing`),this.warnedFeatures.add(e));}isProEnabled(){return this.hasFeature("*")}isAIEnabled(){return this.hasFeature(E.NAV_AI)}getStatus(){return this.status}getPayload(){return this.payload}getEnabledFeatures(){return Array.from(this.enabledFeatures)}async verifySignature(e,t){try{if(typeof crypto>"u"||!crypto.subtle)return !1;let i=new TextEncoder().encode(e),r=Uint8Array.from(atob(t),a=>a.charCodeAt(0)),o=Uint8Array.from(atob(B.PUBLIC_KEY_SPKI),a=>a.charCodeAt(0)),c=await crypto.subtle.importKey("spki",o,{name:"ECDSA",namedCurve:"P-256"},!0,["verify"]);return await crypto.subtle.verify({name:"ECDSA",hash:{name:"SHA-256"}},c,r,i)}catch{return false}}};B.PUBLIC_KEY_SPKI="MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEk6onJmJkWtRFusiSS69dcdONX4xL2Snm4AZO+dYjSynfJiL4x7trEy80MImLftknBWT/qI0z7ox4Q7VNL3te6A==";var G=B;var he=class{constructor(e){this.route=[];this.instructions=[];this.forwardCum=new Float64Array(0);this.reverseCum=new Float64Array(0);this.snappedCoord=null;this.rawGpsCoord=null;this.bearing=0;this.rawGpsBearing=null;this.routeIndex=0;this.maxProgressIdx=0;this.prevSegment=null;this.distanceToRoute=0;this.corridor=25;this.prevDistToNext=null;this.currentSpeed=0;this.lastGpsTimestamp=0;this.instrIdx=0;this.linearOffset=0;this.hasArrived=false;this.listeners=new Map;this.options={licenseKey:e?.licenseKey??"",isDev:e?.isDev??false,isCircuit:e?.isCircuit??false,kalmanQ:e?.kalmanQ??1e-5,kalmanRBase:e?.kalmanRBase??5e-5,baseCorridorMeters:e?.baseCorridorMeters??25,maxCorridorMeters:e?.maxCorridorMeters??60,arrivalThresholdMeters:e?.arrivalThresholdMeters??30,bearingSmoothingAlpha:e?.bearingSmoothingAlpha??.3},this.kalman=new M(this.options.kalmanQ,this.options.kalmanRBase),this.snapper=new T({useBearingPenalty:true,useInertiaPenalty:true}),this.deadReckoning=new D,this.deviationDetector=new N,this.lifecycleFSM=new w,this.offRouteFSM=new F,this.license=new G(this.options.licenseKey,this.options.isDev);}has(e){let t=this.license.hasFeature(e);return t||this.license.warnOnce(e),t}setRoute(e,t){this.route=e,this.instructions=t??[],this.forwardCum=K(e),this.reverseCum=j(e),this.routeIndex=0,this.maxProgressIdx=0,this.prevSegment=null,this.instrIdx=0,this.linearOffset=0,this.hasArrived=false,this.snappedCoord=null,this.prevDistToNext=null,this.lifecycleFSM.dispatch({type:"ROUTE_LOADED"}),this.emit("routeLoaded",{routeLength:e.length});}startNavigation(){this.lifecycleFSM.dispatch({type:"USER_START_NAVIGATION"}),this.emit("navigationStarted");}update(e){if(this.rawGpsCoord=e.coord,this.rawGpsBearing=e.bearing,this.currentSpeed=e.speed,this.lastGpsTimestamp=e.timestamp,this.route.length<2)return this.getState();let[t,n]=this.kalman.update(e.coord[0],e.coord[1],e.accuracy??10,e.timestamp),i=[t,n],r=Math.max(0,this.routeIndex-2),o=this.snapper.snap(i,this.route,r,25,this.options.isCircuit,this.maxProgressIdx,this.routeIndex,e.bearing,this.prevSegment);this.corridor=Z(e.speed,e.accuracy,this.options.baseCorridorMeters,this.options.maxCorridorMeters);let c=te(this.routeIndex,o.idx,this.route,o.dist,80);if(this.prevSegment=this.routeIndex,this.routeIndex=c,this.maxProgressIdx=Math.max(this.maxProgressIdx,c),this.snappedCoord=o.proj,this.distanceToRoute=o.dist,this.forwardCum.length>c){let a=this.forwardCum[c]??0;this.linearOffset=oe(this.linearOffset,a,.15);}if(this.has(E.ADVANCED_BEARING)?this.bearing=ee(this.bearing,this.route,this.routeIndex,e.speed,e.bearing,this.options.bearingSmoothingAlpha):e.bearing!==null&&(this.bearing=e.bearing),this.has(E.COMPOUND_DEVIATION)&&(this.offRouteFSM.state==="ON_ROUTE"||this.offRouteFSM.state==="DEVIATING")){let a=Ae(i,this.route,r,25),u=this.deviationDetector.evaluate(i,e.bearing,e.speed,a,this.route,this.corridor,this.prevDistToNext);if(a.segmentIndex<this.route.length-1){let d=this.route[Math.min(a.segmentIndex+2,this.route.length-1)];this.prevDistToNext=m(i[1],i[0],d[1],d[0]);}u.confirmed?this.offRouteFSM.state==="ON_ROUTE"?this.offRouteFSM.dispatch({type:"DEVIATION_SUSPECTED",deviationSegmentIdx:a.segmentIndex}):this.offRouteFSM.isDeviationConfirmed()&&(this.offRouteFSM.dispatch({type:"DEVIATION_CONFIRMED",deviationSegmentIdx:a.segmentIndex}),this.emit("deviation",{anchorIndex:a.segmentIndex,ghostCoord:o.proj})):this.offRouteFSM.state==="DEVIATING"&&this.offRouteFSM.dispatch({type:"RESUME"});}else this.has(E.COMPOUND_DEVIATION)||(o.dist>this.corridor&&this.offRouteFSM.state==="ON_ROUTE"?(this.offRouteFSM.dispatch({type:"DEVIATION_SUSPECTED",deviationSegmentIdx:o.idx}),this.emit("deviation",{anchorIndex:o.idx,ghostCoord:o.proj})):o.dist<=this.corridor&&this.offRouteFSM.state!=="ON_ROUTE"&&this.offRouteFSM.dispatch({type:"RESUME"}));if(this.lifecycleFSM.state==="APPROACHING_ROUTE"&&o.dist<this.corridor&&this.lifecycleFSM.dispatch({type:"ROUTE_JOINED",data:{index:o.idx}}),this.instructions.length>0){let a=ne(this.routeIndex,this.instructions,this.instrIdx,this.route,i,e.bearing,this.linearOffset,e.speed);a.nextIdx!==this.instrIdx&&(this.instrIdx=a.nextIdx),a.wasTriggered&&a.instruction&&(this.emit("instruction",a.instruction),this.instrIdx=a.nextIdx+1);}if(!this.hasArrived&&this.route.length>0){let a=this.route[this.route.length-1],u=m(i[1],i[0],a[1],a[0]),d=this.options.isCircuit?this.routeIndex>=this.route.length*.8:true;u<this.options.arrivalThresholdMeters&&d&&(this.hasArrived=true,this.lifecycleFSM.dispatch({type:"NAVIGATION_FINISHED"}),this.emit("arrival"));}return this.has(E.DEAD_RECKONING)&&this.deadReckoning.setAnchor(o.proj,o.idx,e.timestamp),this.getState()}tick(e){if(this.has(E.DEAD_RECKONING)){let t=this.deadReckoning.predict(this.currentSpeed,this.bearing,e);t.isActive&&(this.snappedCoord=t.predictedCoord);}if(this.lastGpsTimestamp>0){let t=(e-this.lastGpsTimestamp)/1e3;t>0&&t<10&&(this.linearOffset=re(this.linearOffset,this.currentSpeed,t));}return this.getState()}getState(){let e=this.route.length>0&&this.reverseCum.length>this.routeIndex?this.reverseCum[this.routeIndex]??null:null,t=this.instrIdx<this.instructions.length?(()=>{let i=this.instructions[this.instrIdx];return i.cumulativeOffset!==void 0?Math.max(0,i.cumulativeOffset-this.linearOffset):null})():null,n=this.lastGpsTimestamp>0?Date.now()-this.lastGpsTimestamp:0;return {snappedCoord:this.snappedCoord,rawGpsCoord:this.rawGpsCoord,bearing:this.bearing,rawGpsBearing:this.rawGpsBearing,routeIndex:this.routeIndex,distanceToRoute:this.distanceToRoute,corridor:this.corridor,isOffRoute:this.offRouteFSM.state!=="ON_ROUTE"&&this.offRouteFSM.state!=="DEVIATING",progressIndex:this.routeIndex,hasArrived:this.hasArrived,offRouteState:this.offRouteFSM.state,lifecycleState:this.lifecycleFSM.state,distanceToDestination:e,distanceToNextInstruction:t,nextInstructionIndex:this.instrIdx<this.instructions.length?this.instrIdx:null,currentSpeed:this.currentSpeed,isGpsStale:n>3e3,gpsAgeMs:n,licenseStatus:this.license.getStatus()}}destroy(){this.listeners.clear(),this.route=[],this.instructions=[],this.kalman.reset(),this.deadReckoning.clearAnchor(),this.lifecycleFSM.reset(),this.offRouteFSM.reset();}on(e,t){this.listeners.has(e)||this.listeners.set(e,[]),this.listeners.get(e).push(t);}off(e,t){let n=this.listeners.get(e);if(n){let i=n.indexOf(t);i>=0&&n.splice(i,1);}}emit(e,...t){let n=this.listeners.get(e);if(n)for(let i of n)i(...t);}getLicenseManager(){return this.license}getRoute(){return this.route}getInstructions(){return this.instructions}getOffRouteFSM(){return this.offRouteFSM}getLifecycleFSM(){return this.lifecycleFSM}};
7
+ </gpx>`}fromGeoJSON(e){if(this.waypoints=[],e.type==="FeatureCollection"&&e.features){for(let t of e.features)if(t.geometry.type==="Point"){let n=t.geometry.coordinates;this.addWaypoint(n);}else if(t.geometry.type==="LineString"){let n=t.geometry.coordinates;for(let i of n)this.addWaypoint(i);}}}toGeoJSON(){let e=this.waypoints.map((n,i)=>({type:"Feature",geometry:{type:"Point",coordinates:n.coord},properties:{id:n.id,order:i,name:n.options?.name??null,silent:n.options?.silent??false}})),t={type:"Feature",geometry:{type:"LineString",coordinates:this.waypoints.map(n=>n.coord)},properties:{type:"route"}};return {type:"FeatureCollection",features:[...e,t]}}clear(){this.waypoints=[];}};var ae=class{constructor(e){this.config={baseUrl:e.baseUrl.replace(/\/$/,""),profile:e.profile??"driving",maxWaypointsPerChunk:e.maxWaypointsPerChunk??100};}async getRoute(e,t){if(e.length<2)throw new Error("[OSRM] At least 2 waypoints required");let n=t?.profile??this.config.profile;return e.length<=this.config.maxWaypointsPerChunk?this.fetchRoute(e,n,t):this.fetchChunked(e,n,t)}async fetchRoute(e,t,n){let i=e.map(d=>`${d[0]},${d[1]}`).join(";"),r=n?.steps!==false?"true":"false",o=`${this.config.baseUrl}/route/v1/${t}/${i}?overview=full&geometries=geojson&steps=${r}&annotations=false`,c=await fetch(o);if(!c.ok)throw new Error(`[OSRM] HTTP ${c.status}: ${c.statusText}`);let a=await c.json();if(a.code!=="Ok"||!a.routes?.length)throw new Error(`[OSRM] No route found: ${a.code}`);let u=a.routes[0];return {geometry:u.geometry.coordinates,distance:u.distance,duration:u.duration,legs:(u.legs??[]).map(d=>({distance:d.distance,duration:d.duration,steps:(d.steps??[]).map(l=>({distance:l.distance,duration:l.duration,geometry:l.geometry?.coordinates??[],maneuver:{type:l.maneuver?.type??"unknown",modifier:l.maneuver?.modifier,location:l.maneuver?.location??[0,0],bearingBefore:l.maneuver?.bearing_before??0,bearingAfter:l.maneuver?.bearing_after??0,instruction:l.name??void 0},name:l.name}))})),raw:a}}async fetchChunked(e,t,n){let i=this.config.maxWaypointsPerChunk,r=[];for(let l=0;l<e.length;l+=i-1){let p=e.slice(l,l+i);if(p.length<2||(r.push(p),l+i>=e.length))break}let o=await Promise.all(r.map(l=>this.fetchRoute(l,t,n))),c=[],a=0,u=0,d=[];for(let l=0;l<o.length;l++){let p=o[l];c.push(...l===0?p.geometry:p.geometry.slice(1)),a+=p.distance,u+=p.duration,d.push(...p.legs);}return {geometry:c,distance:a,duration:u,legs:d,raw:o.map(l=>l.raw)}}};var ce=class{constructor(e){this.config={baseUrl:e.baseUrl.replace(/\/$/,""),costing:e.costing??"auto",apiKey:e.apiKey??"",maxWaypointsPerChunk:e.maxWaypointsPerChunk??50};}async getRoute(e,t){if(e.length<2)throw new Error("[Valhalla] At least 2 waypoints required");let n=this.profileToCosting(t?.profile);return e.length<=this.config.maxWaypointsPerChunk?this.fetchRoute(e,n,t):this.fetchChunked(e,n,t)}profileToCosting(e){switch(e){case "cycling":return "bicycle";case "walking":return "pedestrian";default:return this.config.costing}}async fetchRoute(e,t,n){let i=e.map(p=>({lon:p[0],lat:p[1],type:"break"})),r=JSON.stringify({locations:i,costing:t,directions_options:{units:"kilometers"}}),o={"Content-Type":"application/json"};this.config.apiKey&&(o.api_key=this.config.apiKey);let c=await fetch(`${this.config.baseUrl}/route`,{method:"POST",headers:o,body:r});if(!c.ok){let p=await c.text();throw new Error(`[Valhalla] HTTP ${c.status}: ${p}`)}let a=await c.json(),u=this.decodePolyline(a.trip?.legs?.[0]?.shape??""),d=a.trip?.summary??{},l=(a.trip?.legs??[]).map(p=>({distance:(p.summary?.length??0)*1e3,duration:p.summary?.time??0,steps:(p.maneuvers??[]).map(g=>({distance:(g.length??0)*1e3,duration:g.time??0,geometry:[],maneuver:{type:g.type?.toString()??"unknown",modifier:void 0,location:e[0],bearingBefore:g.begin_heading??0,bearingAfter:g.end_heading??0,instruction:g.instruction},name:g.street_names?.[0]}))}));return {geometry:u,distance:(d.length??0)*1e3,duration:d.time??0,legs:l,raw:a}}decodePolyline(e,t=6){let n=Math.pow(10,t),i=[],r=0,o=0,c=0;for(;r<e.length;){let a=0,u=0,d;do d=e.charCodeAt(r++)-63,u|=(d&31)<<a,a+=5;while(d>=32);let l=(u&1)!==0?~(u>>1):u>>1;o+=l,a=0,u=0;do d=e.charCodeAt(r++)-63,u|=(d&31)<<a,a+=5;while(d>=32);let p=(u&1)!==0?~(u>>1):u>>1;c+=p,i.push([c/n,o/n]);}return i}async fetchChunked(e,t,n){let i=this.config.maxWaypointsPerChunk,r=[];for(let l=0;l<e.length;l+=i-1){let p=e.slice(l,l+i);if(p.length<2||(r.push(p),l+i>=e.length))break}let o=await Promise.all(r.map(l=>this.fetchRoute(l,t,n))),c=[],a=0,u=0,d=[];for(let l=0;l<o.length;l++){let p=o[l];c.push(...l===0?p.geometry:p.geometry.slice(1)),a+=p.distance,u+=p.duration,d.push(...p.legs);}return {geometry:c,distance:a,duration:u,legs:d,raw:o.map(l=>l.raw)}}};var Ze="https://api.openrouteservice.org/v2/directions";var le=class{constructor(e){this.config={apiKey:e.apiKey,profile:e.profile??"driving-car",baseUrl:e.baseUrl??Ze,maxWaypointsPerChunk:e.maxWaypointsPerChunk??50};}async getRoute(e,t){if(e.length<2)throw new Error("[ORS] At least 2 waypoints required");let n=this.profileFromOptions(t);return e.length<=this.config.maxWaypointsPerChunk?this.fetchRoute(e,n,t):this.fetchChunked(e,n,t)}profileFromOptions(e){switch(e?.profile){case "cycling":return "cycling-regular";case "walking":return "foot-walking";default:return this.config.profile}}async fetchRoute(e,t,n){let i=JSON.stringify({coordinates:e,instructions:n?.steps!==false,language:n?.language??"en"}),r=await fetch(`${this.config.baseUrl}/${t}/geojson`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:this.config.apiKey},body:i});if(!r.ok){let l=await r.text();throw new Error(`[ORS] HTTP ${r.status}: ${l}`)}let o=await r.json(),c=o.features?.[0];if(!c)throw new Error("[ORS] No route returned");let a=c.properties?.summary??{},d=(c.properties?.segments??[]).map(l=>({distance:l.distance??0,duration:l.duration??0,steps:(l.steps??[]).map(p=>({distance:p.distance??0,duration:p.duration??0,geometry:[],maneuver:{type:p.type?.toString()??"unknown",modifier:void 0,location:e[0],bearingBefore:0,bearingAfter:0,instruction:p.instruction},name:p.name}))}));return {geometry:c.geometry?.coordinates??[],distance:a.distance??0,duration:a.duration??0,legs:d,raw:o}}async fetchChunked(e,t,n){let i=this.config.maxWaypointsPerChunk,r=[];for(let l=0;l<e.length;l+=i-1){let p=e.slice(l,l+i);if(p.length<2||(r.push(p),l+i>=e.length))break}let o=await Promise.all(r.map(l=>this.fetchRoute(l,t,n))),c=[],a=0,u=0,d=[];for(let l=0;l<o.length;l++){let p=o[l];c.push(...l===0?p.geometry:p.geometry.slice(1)),a+=p.distance,u+=p.duration,d.push(...p.legs);}return {geometry:c,distance:a,duration:u,legs:d,raw:o.map(l=>l.raw)}}};var et={earlyTriggerMeters:120,lateTriggerMeters:20,reannounceMeters:300},ue=class{constructor(e){this.instructions=[];this.triggeredIds=new Set;this.reannouncedIds=new Set;this.options={...et,...e};}setInstructions(e){this.instructions=[...e],this.triggeredIds.clear(),this.reannouncedIds.clear();}update(e){let{distanceToNextInstruction:t,nextInstructionIndex:n}=e;if(n===null||t===null||n>=this.instructions.length)return null;let i=this.instructions[n],r=this.instrId(n),o=t;if(this.triggeredIds.has(r))return this.options.reannounceMeters>0&&!this.reannouncedIds.has(r)&&o<=this.options.reannounceMeters&&o>this.options.earlyTriggerMeters?(this.reannouncedIds.add(r),this.makeCue(i,o,"low")):null;if(o>this.options.earlyTriggerMeters)return null;if(o<-this.options.lateTriggerMeters)return this.triggeredIds.add(r),null;this.triggeredIds.add(r);let c=o<30?"urgent":o<60?"high":"normal";return this.makeCue(i,o,c)}onInstruction(e,t){let n=t.distanceToNextInstruction??0;return this.makeCue(e,n,"high")}reset(){this.triggeredIds.clear(),this.reannouncedIds.clear();}instrId(e){let t=this.instructions[e];return `${e}_${t?.geometryIndex??0}`}makeCue(e,t,n){return {instruction:e,text:e.text??e.instruction??"",distanceMetres:Math.max(0,t),priority:n,triggerId:`${e.geometryIndex}_${t.toFixed(0)}`}}};var tt={speedWindowSize:6,minimumSpeedMs:.5,fallbackSpeedMs:8.33},de=class{constructor(e){this.speedSamples=[];this.options={...tt,...e},this.lastReliableSpeed=this.options.fallbackSpeedMs;}update(e){let t=e.distanceToDestination??0,n=e.currentSpeed??0;this.speedSamples.push(n),this.speedSamples.length>this.options.speedWindowSize&&this.speedSamples.shift();let i=this.rollingAverage();i>=this.options.minimumSpeedMs&&(this.lastReliableSpeed=i);let r=i>=this.options.minimumSpeedMs?i:this.lastReliableSpeed,o=this.speedSamples.length>=Math.ceil(this.options.speedWindowSize/2)&&r>0&&t>0,c=r>0?t/r:0;return {etaSeconds:Math.round(c),etaMs:Date.now()+c*1e3,distanceRemainingM:t,averageSpeedMs:i,isReliable:o}}reset(){this.speedSamples=[],this.lastReliableSpeed=this.options.fallbackSpeedMs;}rollingAverage(){return this.speedSamples.length===0?0:this.speedSamples.reduce((t,n)=>t+n,0)/this.speedSamples.length}};var nt={dwellThresholdMs:1e4},pe=class{constructor(e){this.fences=new Map;this.states=new Map;this.options={...nt,...e};}addCircle(e,t,n,i){return this.fences.set(e,{kind:"circle",id:e,center:t,radiusM:n,meta:i}),this.states.set(e,{inside:false,enteredAtMs:null}),e}addPolygon(e,t,n){if(t.length<3)throw new Error("[Geofencing] Polygon requires at least 3 coordinates");return this.fences.set(e,{kind:"polygon",id:e,coords:t,meta:n}),this.states.set(e,{inside:false,enteredAtMs:null}),e}remove(e){this.fences.delete(e),this.states.delete(e);}clear(){this.fences.clear(),this.states.clear();}listIds(){return Array.from(this.fences.keys())}update(e){if(!e)return [];let t=[],n=Date.now();for(let[i,r]of this.fences){let o=this.states.get(i),c=this.contains(r,e);if(!o.inside&&c)o.inside=true,o.enteredAtMs=n,t.push({id:i,type:"enter",coord:e,meta:r.meta});else if(o.inside&&!c){o.inside=false;let a=o.enteredAtMs?n-o.enteredAtMs:0;o.enteredAtMs=null,t.push({id:i,type:"exit",coord:e,dwellMs:a,meta:r.meta});}else if(o.inside&&o.enteredAtMs!==null){let a=n-o.enteredAtMs;a>=this.options.dwellThresholdMs&&(t.push({id:i,type:"dwell",coord:e,dwellMs:a,meta:r.meta}),o.enteredAtMs=n);}}return t}isInside(e,t){let n=this.fences.get(e);return n?this.contains(n,t):false}contains(e,t){return e.kind==="circle"?m(t[1],t[0],e.center[1],e.center[0])<=e.radiusM:this.pointInPolygon(t,e.coords)}pointInPolygon(e,t){let[n,i]=e,r=false,o=t.length;for(let c=0,a=o-1;c<o;a=c++){let[u,d]=t[c],[l,p]=t[a];d>i!=p>i&&n<(l-u)*(i-d)/(p-d)+u&&(r=!r);}return r}};var E={GPS_SMOOTHING:"gps_smoothing",BASIC_SNAPPING:"basic_snapping",ADVANCED_SNAPPING:"advanced_snapping",DEAD_RECKONING:"dead_reckoning",ROUTE_OFFSET:"route_offset",PROGRESS_TRACKING:"progress_tracking",ADVANCED_BEARING:"advanced_bearing",COMPOUND_DEVIATION:"compound_deviation",PARALLEL_ROAD_RESOLUTION:"parallel_road_resolution",U_TURN_PROTECTION:"u_turn_protection",VOICE_TRIGGERS:"voice_triggers",ETA:"eta",GEOFENCING:"geofencing",NAV_AI:"nav_ai"};var B=class B{constructor(e,t){this.status="MISSING";this.payload=null;this.enabledFeatures=new Set;this.warnedFeatures=new Set;this.isDev=false;this.isDev=t??false,e&&this.validate(e);}async validate(e){try{if(this.status==="VALID"&&this.payload?.bid==="dev.bypass")return "VALID";let[t,n]=e.split(".");if(!t||!n)return this.status="INVALID",this.status;let i=atob(t);if(this.payload=JSON.parse(i),!this.payload||this.payload.exp<Date.now())return this.status="EXPIRED",this.status;if(!await this.verifySignature(t,n))return this.status="INVALID",this.status;this.status="VALID",this.extractFeatures(),console.log(`\u{1F511} [NavCore] License validation complete. Plan: ${this.isProEnabled()?"pro":"core"}`);}catch{this.status="INVALID";}return this.status}extractFeatures(){if(this.enabledFeatures.clear(),!!this.payload){if(Array.isArray(this.payload.f))for(let e of this.payload.f)this.enabledFeatures.add(e);this.payload.t==="pro"&&this.enabledFeatures.add("*");}}hasFeature(e){return this.isDev?true:this.status!=="VALID"?false:this.enabledFeatures.has("*")?true:this.enabledFeatures.has(e)}warnOnce(e){this.warnedFeatures.has(e)||(console.warn(`[NavCore] Feature '${e}' requires a Pro license`),this.warnedFeatures.add(e));}isProEnabled(){return this.hasFeature("*")}isAIEnabled(){return this.hasFeature(E.NAV_AI)}getStatus(){return this.status}getPayload(){return this.payload}getEnabledFeatures(){return Array.from(this.enabledFeatures)}async verifySignature(e,t){try{if(typeof crypto>"u"||!crypto.subtle)return !1;let i=new TextEncoder().encode(e),r=Uint8Array.from(atob(t),a=>a.charCodeAt(0)),o=Uint8Array.from(atob(B.PUBLIC_KEY_SPKI),a=>a.charCodeAt(0)),c=await crypto.subtle.importKey("spki",o,{name:"ECDSA",namedCurve:"P-256"},!0,["verify"]);return await crypto.subtle.verify({name:"ECDSA",hash:{name:"SHA-256"}},c,r,i)}catch{return false}}};B.PUBLIC_KEY_SPKI="MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEk6onJmJkWtRFusiSS69dcdONX4xL2Snm4AZO+dYjSynfJiL4x7trEy80MImLftknBWT/qI0z7ox4Q7VNL3te6A==";var G=B;var he=class{constructor(e){this.route=[];this.instructions=[];this.forwardCum=new Float64Array(0);this.reverseCum=new Float64Array(0);this.snappedCoord=null;this.rawGpsCoord=null;this.bearing=0;this.rawGpsBearing=null;this.routeIndex=0;this.maxProgressIdx=0;this.prevSegment=null;this.distanceToRoute=0;this.corridor=25;this.prevDistToNext=null;this.currentSpeed=0;this.lastGpsTimestamp=0;this.instrIdx=0;this.linearOffset=0;this.hasArrived=false;this.listeners=new Map;this.options={licenseKey:e?.licenseKey??"",isDev:e?.isDev??false,isCircuit:e?.isCircuit??false,kalmanQ:e?.kalmanQ??1e-5,kalmanRBase:e?.kalmanRBase??5e-5,baseCorridorMeters:e?.baseCorridorMeters??25,maxCorridorMeters:e?.maxCorridorMeters??60,arrivalThresholdMeters:e?.arrivalThresholdMeters??30,bearingSmoothingAlpha:e?.bearingSmoothingAlpha??.3},this.kalman=new M(this.options.kalmanQ,this.options.kalmanRBase),this.snapper=new T({useBearingPenalty:true,useInertiaPenalty:true}),this.deadReckoning=new D,this.deviationDetector=new N,this.lifecycleFSM=new w,this.offRouteFSM=new F,this.license=new G(this.options.licenseKey,this.options.isDev);}has(e){let t=this.license.hasFeature(e);return t||this.license.warnOnce(e),t}setRoute(e,t){this.route=e,this.instructions=t??[],this.forwardCum=K(e),this.reverseCum=j(e),this.routeIndex=0,this.maxProgressIdx=0,this.prevSegment=null,this.instrIdx=0,this.linearOffset=0,this.hasArrived=false,this.snappedCoord=null,this.prevDistToNext=null,this.lifecycleFSM.dispatch({type:"ROUTE_LOADED"}),this.emit("routeLoaded",{routeLength:e.length});}startNavigation(){this.lifecycleFSM.dispatch({type:"USER_START_NAVIGATION"}),this.emit("navigationStarted");}update(e){if(this.rawGpsCoord=e.coord,this.rawGpsBearing=e.bearing,this.currentSpeed=e.speed,this.lastGpsTimestamp=e.timestamp,this.route.length<2)return this.getState();let[t,n]=this.kalman.update(e.coord[0],e.coord[1],e.accuracy??10,e.timestamp),i=[t,n],r=Math.max(0,this.routeIndex-2),o=this.snapper.snap(i,this.route,r,25,this.options.isCircuit,this.maxProgressIdx,this.routeIndex,e.bearing,this.prevSegment);this.corridor=Z(e.speed,e.accuracy,this.options.baseCorridorMeters,this.options.maxCorridorMeters);let c=te(this.routeIndex,o.idx,this.route,o.dist,80);if(this.prevSegment=this.routeIndex,this.routeIndex=c,this.maxProgressIdx=Math.max(this.maxProgressIdx,c),this.snappedCoord=o.proj,this.distanceToRoute=o.dist,this.forwardCum.length>c){let a=this.forwardCum[c]??0;this.linearOffset=oe(this.linearOffset,a,.15);}if(this.has(E.ADVANCED_BEARING)?this.bearing=ee(this.bearing,this.route,this.routeIndex,e.speed,e.bearing,this.options.bearingSmoothingAlpha):e.bearing!==null&&(this.bearing=e.bearing),this.has(E.COMPOUND_DEVIATION)&&(this.offRouteFSM.state==="ON_ROUTE"||this.offRouteFSM.state==="DEVIATING")){let a=Ae(i,this.route,r,25),u=this.deviationDetector.evaluate(i,e.bearing,e.speed,a,this.route,this.corridor,this.prevDistToNext);if(a.segmentIndex<this.route.length-1){let d=this.route[Math.min(a.segmentIndex+2,this.route.length-1)];this.prevDistToNext=m(i[1],i[0],d[1],d[0]);}u.confirmed?this.offRouteFSM.state==="ON_ROUTE"?this.offRouteFSM.dispatch({type:"DEVIATION_SUSPECTED",deviationSegmentIdx:a.segmentIndex}):this.offRouteFSM.isDeviationConfirmed()&&(this.offRouteFSM.dispatch({type:"DEVIATION_CONFIRMED",deviationSegmentIdx:a.segmentIndex}),this.emit("deviation",{anchorIndex:a.segmentIndex,ghostCoord:o.proj})):this.offRouteFSM.state==="DEVIATING"&&this.offRouteFSM.dispatch({type:"RESUME"});}else this.has(E.COMPOUND_DEVIATION)||(o.dist>this.corridor&&this.offRouteFSM.state==="ON_ROUTE"?(this.offRouteFSM.dispatch({type:"DEVIATION_SUSPECTED",deviationSegmentIdx:o.idx}),this.emit("deviation",{anchorIndex:o.idx,ghostCoord:o.proj})):o.dist<=this.corridor&&this.offRouteFSM.state!=="ON_ROUTE"&&this.offRouteFSM.dispatch({type:"RESUME"}));if(this.lifecycleFSM.state==="APPROACHING_ROUTE"&&o.dist<this.corridor&&this.lifecycleFSM.dispatch({type:"ROUTE_JOINED",data:{index:o.idx}}),this.instructions.length>0){let a=ne(this.routeIndex,this.instructions,this.instrIdx,this.route,i,e.bearing,this.linearOffset,e.speed);a.nextIdx!==this.instrIdx&&(this.instrIdx=a.nextIdx),a.wasTriggered&&a.instruction&&(this.emit("instruction",a.instruction),this.instrIdx=a.nextIdx+1);}if(!this.hasArrived&&this.route.length>0){let a=this.route[this.route.length-1],u=m(i[1],i[0],a[1],a[0]),d=this.options.isCircuit?this.routeIndex>=this.route.length*.8:true;u<this.options.arrivalThresholdMeters&&d&&(this.hasArrived=true,this.lifecycleFSM.dispatch({type:"NAVIGATION_FINISHED"}),this.emit("arrival"));}return this.has(E.DEAD_RECKONING)&&this.deadReckoning.setAnchor(o.proj,o.idx,e.timestamp),this.getState()}tick(e){if(this.has(E.DEAD_RECKONING)){let t=this.deadReckoning.predict(this.currentSpeed,this.bearing,e);t.isActive&&(this.snappedCoord=t.predictedCoord);}if(this.lastGpsTimestamp>0){let t=(e-this.lastGpsTimestamp)/1e3;t>0&&t<10&&(this.linearOffset=re(this.linearOffset,this.currentSpeed,t));}return this.getState()}getState(){let e=this.route.length>0&&this.reverseCum.length>this.routeIndex?this.reverseCum[this.routeIndex]??null:null,t=this.instrIdx<this.instructions.length?(()=>{let i=this.instructions[this.instrIdx];return i.cumulativeOffset!==void 0?Math.max(0,i.cumulativeOffset-this.linearOffset):null})():null,n=this.lastGpsTimestamp>0?Date.now()-this.lastGpsTimestamp:0;return {snappedCoord:this.snappedCoord,rawGpsCoord:this.rawGpsCoord,bearing:this.bearing,rawGpsBearing:this.rawGpsBearing,routeIndex:this.routeIndex,distanceToRoute:this.distanceToRoute,corridor:this.corridor,isOffRoute:this.offRouteFSM.state!=="ON_ROUTE"&&this.offRouteFSM.state!=="DEVIATING",progressIndex:this.routeIndex,hasArrived:this.hasArrived,offRouteState:this.offRouteFSM.state,lifecycleState:this.lifecycleFSM.state,distanceToDestination:e,distanceToNextInstruction:t,nextInstructionIndex:this.instrIdx<this.instructions.length?this.instrIdx:null,currentSpeed:this.currentSpeed,isGpsStale:n>3e3,gpsAgeMs:n,licenseStatus:this.license.getStatus()}}destroy(){this.listeners.clear(),this.route=[],this.instructions=[],this.kalman.reset(),this.deadReckoning.clearAnchor(),this.lifecycleFSM.reset(),this.offRouteFSM.reset();}on(e,t){this.listeners.has(e)||this.listeners.set(e,[]),this.listeners.get(e).push(t);}off(e,t){let n=this.listeners.get(e);if(n){let i=n.indexOf(t);i>=0&&n.splice(i,1);}}emit(e,...t){let n=this.listeners.get(e);if(n)for(let i of n)i(...t);}getLicenseManager(){return this.license}getRoute(){return this.route}getInstructions(){return this.instructions}getOffRouteFSM(){return this.offRouteFSM}getLifecycleFSM(){return this.lifecycleFSM}};
8
8
  exports.Constants=ve;exports.CustomRouteBuilder=se;exports.DeadReckoningEngine=D;exports.DeviationDetector=N;exports.ETAEngine=de;exports.FEATURES=E;exports.GeofencingEngine=pe;exports.InstructionEditor=ie;exports.KalmanFilter2D=M;exports.LicenseManager=G;exports.NavCore=he;exports.NavigationFSM=w;exports.OSRMDirectionsProvider=ae;exports.OffRouteFSM=F;exports.OpenRouteServiceProvider=le;exports.ParallelRoadResolver=Q;exports.RouteSnapper=T;exports.ValhallaDirectionsProvider=ce;exports.VoiceTriggerEngine=ue;exports.advancePredictor=re;exports.applyBearingSmoothing=ee;exports.applyGpsCorrection=oe;exports.blendBearing=U;exports.buildForwardCumulativeDistances=K;exports.buildReverseCumulativeDistances=j;exports.calculateDynamicCorridor=Z;exports.calculateRobustBearing=Me;exports.computeDeadReckoningOnRoute=Oe;exports.destinationPoint=V;exports.geoGetBearing=we;exports.getBearingBetweenPoints=f;exports.getSpeedAdaptiveAlpha=_e;exports.haversineDistance=m;exports.interpolatePoint=H;exports.kalmanUpdate2D=me;exports.normalizeAngle=y;exports.pointAtOffset=ze;exports.projectPointOnSegment=W;exports.resolveNextInstruction=ne;exports.validateProgressUpdate=te;
package/dist/index.mjs CHANGED
@@ -4,5 +4,5 @@ ${this.waypoints.map(t=>` <wpt lat="${t.coord[1]}" lon="${t.coord[0]}">`+(t.o
4
4
  <name>${t.options.name}</name>`:"")+`
5
5
  </wpt>`).join(`
6
6
  `)}
7
- </gpx>`}fromGeoJSON(e){if(this.waypoints=[],e.type==="FeatureCollection"&&e.features){for(let t of e.features)if(t.geometry.type==="Point"){let n=t.geometry.coordinates;this.addWaypoint(n);}else if(t.geometry.type==="LineString"){let n=t.geometry.coordinates;for(let i of n)this.addWaypoint(i);}}}toGeoJSON(){let e=this.waypoints.map((n,i)=>({type:"Feature",geometry:{type:"Point",coordinates:n.coord},properties:{id:n.id,order:i,name:n.options?.name??null,silent:n.options?.silent??false}})),t={type:"Feature",geometry:{type:"LineString",coordinates:this.waypoints.map(n=>n.coord)},properties:{type:"route"}};return {type:"FeatureCollection",features:[...e,t]}}clear(){this.waypoints=[];}};var ae=class{constructor(e){this.config={baseUrl:e.baseUrl.replace(/\/$/,""),profile:e.profile??"driving",maxWaypointsPerChunk:e.maxWaypointsPerChunk??100};}async getRoute(e,t){if(e.length<2)throw new Error("[OSRM] At least 2 waypoints required");let n=t?.profile??this.config.profile;return e.length<=this.config.maxWaypointsPerChunk?this.fetchRoute(e,n,t):this.fetchChunked(e,n,t)}async fetchRoute(e,t,n){let i=e.map(d=>`${d[0]},${d[1]}`).join(";"),r=n?.steps!==false?"true":"false",o=`${this.config.baseUrl}/route/v1/${t}/${i}?overview=full&geometries=geojson&steps=${r}&annotations=false`,c=await fetch(o);if(!c.ok)throw new Error(`[OSRM] HTTP ${c.status}: ${c.statusText}`);let a=await c.json();if(a.code!=="Ok"||!a.routes?.length)throw new Error(`[OSRM] No route found: ${a.code}`);let u=a.routes[0];return {geometry:u.geometry.coordinates,distance:u.distance,duration:u.duration,legs:(u.legs??[]).map(d=>({distance:d.distance,duration:d.duration,steps:(d.steps??[]).map(l=>({distance:l.distance,duration:l.duration,geometry:l.geometry?.coordinates??[],maneuver:{type:l.maneuver?.type??"unknown",modifier:l.maneuver?.modifier,location:l.maneuver?.location??[0,0],bearingBefore:l.maneuver?.bearing_before??0,bearingAfter:l.maneuver?.bearing_after??0,instruction:l.name??void 0},name:l.name}))})),raw:a}}async fetchChunked(e,t,n){let i=this.config.maxWaypointsPerChunk,r=[];for(let l=0;l<e.length;l+=i-1){let p=e.slice(l,l+i);if(p.length<2||(r.push(p),l+i>=e.length))break}let o=await Promise.all(r.map(l=>this.fetchRoute(l,t,n))),c=[],a=0,u=0,d=[];for(let l=0;l<o.length;l++){let p=o[l];c.push(...l===0?p.geometry:p.geometry.slice(1)),a+=p.distance,u+=p.duration,d.push(...p.legs);}return {geometry:c,distance:a,duration:u,legs:d,raw:o.map(l=>l.raw)}}};var ce=class{constructor(e){this.config={baseUrl:e.baseUrl.replace(/\/$/,""),costing:e.costing??"auto",apiKey:e.apiKey??"",maxWaypointsPerChunk:e.maxWaypointsPerChunk??50};}async getRoute(e,t){if(e.length<2)throw new Error("[Valhalla] At least 2 waypoints required");let n=this.profileToCosting(t?.profile);return e.length<=this.config.maxWaypointsPerChunk?this.fetchRoute(e,n,t):this.fetchChunked(e,n,t)}profileToCosting(e){switch(e){case "cycling":return "bicycle";case "walking":return "pedestrian";default:return this.config.costing}}async fetchRoute(e,t,n){let i=e.map(p=>({lon:p[0],lat:p[1],type:"break"})),r=JSON.stringify({locations:i,costing:t,directions_options:{units:"kilometers"}}),o={"Content-Type":"application/json"};this.config.apiKey&&(o.api_key=this.config.apiKey);let c=await fetch(`${this.config.baseUrl}/route`,{method:"POST",headers:o,body:r});if(!c.ok){let p=await c.text();throw new Error(`[Valhalla] HTTP ${c.status}: ${p}`)}let a=await c.json(),u=this.decodePolyline(a.trip?.legs?.[0]?.shape??""),d=a.trip?.summary??{},l=(a.trip?.legs??[]).map(p=>({distance:(p.summary?.length??0)*1e3,duration:p.summary?.time??0,steps:(p.maneuvers??[]).map(g=>({distance:(g.length??0)*1e3,duration:g.time??0,geometry:[],maneuver:{type:g.type?.toString()??"unknown",modifier:void 0,location:e[0],bearingBefore:g.begin_heading??0,bearingAfter:g.end_heading??0,instruction:g.instruction},name:g.street_names?.[0]}))}));return {geometry:u,distance:(d.length??0)*1e3,duration:d.time??0,legs:l,raw:a}}decodePolyline(e,t=6){let n=Math.pow(10,t),i=[],r=0,o=0,c=0;for(;r<e.length;){let a=0,u=0,d;do d=e.charCodeAt(r++)-63,u|=(d&31)<<a,a+=5;while(d>=32);let l=(u&1)!==0?~(u>>1):u>>1;o+=l,a=0,u=0;do d=e.charCodeAt(r++)-63,u|=(d&31)<<a,a+=5;while(d>=32);let p=(u&1)!==0?~(u>>1):u>>1;c+=p,i.push([c/n,o/n]);}return i}async fetchChunked(e,t,n){let i=this.config.maxWaypointsPerChunk,r=[];for(let l=0;l<e.length;l+=i-1){let p=e.slice(l,l+i);if(p.length<2||(r.push(p),l+i>=e.length))break}let o=await Promise.all(r.map(l=>this.fetchRoute(l,t,n))),c=[],a=0,u=0,d=[];for(let l=0;l<o.length;l++){let p=o[l];c.push(...l===0?p.geometry:p.geometry.slice(1)),a+=p.distance,u+=p.duration,d.push(...p.legs);}return {geometry:c,distance:a,duration:u,legs:d,raw:o.map(l=>l.raw)}}};var Ze="https://api.openrouteservice.org/v2/directions";var le=class{constructor(e){this.config={apiKey:e.apiKey,profile:e.profile??"driving-car",baseUrl:e.baseUrl??Ze,maxWaypointsPerChunk:e.maxWaypointsPerChunk??50};}async getRoute(e,t){if(e.length<2)throw new Error("[ORS] At least 2 waypoints required");let n=this.profileFromOptions(t);return e.length<=this.config.maxWaypointsPerChunk?this.fetchRoute(e,n,t):this.fetchChunked(e,n,t)}profileFromOptions(e){switch(e?.profile){case "cycling":return "cycling-regular";case "walking":return "foot-walking";default:return this.config.profile}}async fetchRoute(e,t,n){let i=JSON.stringify({coordinates:e,instructions:n?.steps!==false,language:n?.language??"en"}),r=await fetch(`${this.config.baseUrl}/${t}/geojson`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:this.config.apiKey},body:i});if(!r.ok){let l=await r.text();throw new Error(`[ORS] HTTP ${r.status}: ${l}`)}let o=await r.json(),c=o.features?.[0];if(!c)throw new Error("[ORS] No route returned");let a=c.properties?.summary??{},d=(c.properties?.segments??[]).map(l=>({distance:l.distance??0,duration:l.duration??0,steps:(l.steps??[]).map(p=>({distance:p.distance??0,duration:p.duration??0,geometry:[],maneuver:{type:p.type?.toString()??"unknown",modifier:void 0,location:e[0],bearingBefore:0,bearingAfter:0,instruction:p.instruction},name:p.name}))}));return {geometry:c.geometry?.coordinates??[],distance:a.distance??0,duration:a.duration??0,legs:d,raw:o}}async fetchChunked(e,t,n){let i=this.config.maxWaypointsPerChunk,r=[];for(let l=0;l<e.length;l+=i-1){let p=e.slice(l,l+i);if(p.length<2||(r.push(p),l+i>=e.length))break}let o=await Promise.all(r.map(l=>this.fetchRoute(l,t,n))),c=[],a=0,u=0,d=[];for(let l=0;l<o.length;l++){let p=o[l];c.push(...l===0?p.geometry:p.geometry.slice(1)),a+=p.distance,u+=p.duration,d.push(...p.legs);}return {geometry:c,distance:a,duration:u,legs:d,raw:o.map(l=>l.raw)}}};var et={earlyTriggerMeters:120,lateTriggerMeters:20,reannounceMeters:300},ue=class{constructor(e){this.instructions=[];this.triggeredIds=new Set;this.reannouncedIds=new Set;this.options={...et,...e};}setInstructions(e){this.instructions=[...e],this.triggeredIds.clear(),this.reannouncedIds.clear();}update(e){let{distanceToNextInstruction:t,nextInstructionIndex:n}=e;if(n===null||t===null||n>=this.instructions.length)return null;let i=this.instructions[n],r=this.instrId(n),o=t;if(this.triggeredIds.has(r))return this.options.reannounceMeters>0&&!this.reannouncedIds.has(r)&&o<=this.options.reannounceMeters&&o>this.options.earlyTriggerMeters?(this.reannouncedIds.add(r),this.makeCue(i,o,"low")):null;if(o>this.options.earlyTriggerMeters)return null;if(o<-this.options.lateTriggerMeters)return this.triggeredIds.add(r),null;this.triggeredIds.add(r);let c=o<30?"urgent":o<60?"high":"normal";return this.makeCue(i,o,c)}onInstruction(e,t){let n=t.distanceToNextInstruction??0;return this.makeCue(e,n,"high")}reset(){this.triggeredIds.clear(),this.reannouncedIds.clear();}instrId(e){let t=this.instructions[e];return `${e}_${t?.geometryIndex??0}`}makeCue(e,t,n){return {instruction:e,text:e.text??e.instruction??"",distanceMetres:Math.max(0,t),priority:n,triggerId:`${e.geometryIndex}_${t.toFixed(0)}`}}};var tt={speedWindowSize:6,minimumSpeedMs:.5,fallbackSpeedMs:8.33},de=class{constructor(e){this.speedSamples=[];this.options={...tt,...e},this.lastReliableSpeed=this.options.fallbackSpeedMs;}update(e){let t=e.distanceToDestination??0,n=e.currentSpeed??0;this.speedSamples.push(n),this.speedSamples.length>this.options.speedWindowSize&&this.speedSamples.shift();let i=this.rollingAverage();i>=this.options.minimumSpeedMs&&(this.lastReliableSpeed=i);let r=i>=this.options.minimumSpeedMs?i:this.lastReliableSpeed,o=this.speedSamples.length>=Math.ceil(this.options.speedWindowSize/2)&&r>0&&t>0,c=r>0?t/r:0;return {etaSeconds:Math.round(c),etaMs:Date.now()+c*1e3,distanceRemainingM:t,averageSpeedMs:i,isReliable:o}}reset(){this.speedSamples=[],this.lastReliableSpeed=this.options.fallbackSpeedMs;}rollingAverage(){return this.speedSamples.length===0?0:this.speedSamples.reduce((t,n)=>t+n,0)/this.speedSamples.length}};var nt={dwellThresholdMs:1e4},pe=class{constructor(e){this.fences=new Map;this.states=new Map;this.options={...nt,...e};}addCircle(e,t,n,i){return this.fences.set(e,{kind:"circle",id:e,center:t,radiusM:n,meta:i}),this.states.set(e,{inside:false,enteredAtMs:null}),e}addPolygon(e,t,n){if(t.length<3)throw new Error("[Geofencing] Polygon requires at least 3 coordinates");return this.fences.set(e,{kind:"polygon",id:e,coords:t,meta:n}),this.states.set(e,{inside:false,enteredAtMs:null}),e}remove(e){this.fences.delete(e),this.states.delete(e);}clear(){this.fences.clear(),this.states.clear();}listIds(){return Array.from(this.fences.keys())}update(e){if(!e)return [];let t=[],n=Date.now();for(let[i,r]of this.fences){let o=this.states.get(i),c=this.contains(r,e);if(!o.inside&&c)o.inside=true,o.enteredAtMs=n,t.push({id:i,type:"enter",coord:e,meta:r.meta});else if(o.inside&&!c){o.inside=false;let a=o.enteredAtMs?n-o.enteredAtMs:0;o.enteredAtMs=null,t.push({id:i,type:"exit",coord:e,dwellMs:a,meta:r.meta});}else if(o.inside&&o.enteredAtMs!==null){let a=n-o.enteredAtMs;a>=this.options.dwellThresholdMs&&(t.push({id:i,type:"dwell",coord:e,dwellMs:a,meta:r.meta}),o.enteredAtMs=n);}}return t}isInside(e,t){let n=this.fences.get(e);return n?this.contains(n,t):false}contains(e,t){return e.kind==="circle"?m(t[1],t[0],e.center[1],e.center[0])<=e.radiusM:this.pointInPolygon(t,e.coords)}pointInPolygon(e,t){let[n,i]=e,r=false,o=t.length;for(let c=0,a=o-1;c<o;a=c++){let[u,d]=t[c],[l,p]=t[a];d>i!=p>i&&n<(l-u)*(i-d)/(p-d)+u&&(r=!r);}return r}};var E={GPS_SMOOTHING:"gps_smoothing",BASIC_SNAPPING:"basic_snapping",ADVANCED_SNAPPING:"advanced_snapping",DEAD_RECKONING:"dead_reckoning",ROUTE_OFFSET:"route_offset",PROGRESS_TRACKING:"progress_tracking",ADVANCED_BEARING:"advanced_bearing",COMPOUND_DEVIATION:"compound_deviation",PARALLEL_ROAD_RESOLUTION:"parallel_road_resolution",U_TURN_PROTECTION:"u_turn_protection",VOICE_TRIGGERS:"voice_triggers",ETA:"eta",GEOFENCING:"geofencing",NAV_AI:"nav_ai"};var B=class B{constructor(e,t){this.status="MISSING";this.payload=null;this.enabledFeatures=new Set;this.warnedFeatures=new Set;this.isDev=false;this.isDev=t??false,e&&this.validate(e);}async validate(e){try{if(this.status==="VALID"&&this.payload?.bid==="dev.bypass")return "VALID";let[t,n]=e.split(".");if(!t||!n)return this.status="INVALID",this.status;let i=atob(t);if(this.payload=JSON.parse(i),!this.payload||this.payload.exp<Date.now())return this.status="EXPIRED",this.status;if(!await this.verifySignature(t,n))return this.status="INVALID",this.status;this.status="VALID",this.extractFeatures(),console.log(`\u{1F511} [NavCore] License validation complete. Plan: ${this.isProEnabled()?"pro":"core"}`);}catch{this.status="INVALID";}return this.status}extractFeatures(){if(this.enabledFeatures.clear(),!!this.payload){if(Array.isArray(this.payload.f))for(let e of this.payload.f)this.enabledFeatures.add(e);this.payload.t==="pro"&&this.enabledFeatures.add("*");}}hasFeature(e){return this.isDev?true:this.status!=="VALID"?false:this.enabledFeatures.has("*")?true:this.enabledFeatures.has(e)}warnOnce(e){this.warnedFeatures.has(e)||(console.warn(`[NavCore] Feature '${e}' requires a Pro license \u2192 upgrade at https://navcore.dev/pricing`),this.warnedFeatures.add(e));}isProEnabled(){return this.hasFeature("*")}isAIEnabled(){return this.hasFeature(E.NAV_AI)}getStatus(){return this.status}getPayload(){return this.payload}getEnabledFeatures(){return Array.from(this.enabledFeatures)}async verifySignature(e,t){try{if(typeof crypto>"u"||!crypto.subtle)return !1;let i=new TextEncoder().encode(e),r=Uint8Array.from(atob(t),a=>a.charCodeAt(0)),o=Uint8Array.from(atob(B.PUBLIC_KEY_SPKI),a=>a.charCodeAt(0)),c=await crypto.subtle.importKey("spki",o,{name:"ECDSA",namedCurve:"P-256"},!0,["verify"]);return await crypto.subtle.verify({name:"ECDSA",hash:{name:"SHA-256"}},c,r,i)}catch{return false}}};B.PUBLIC_KEY_SPKI="MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEk6onJmJkWtRFusiSS69dcdONX4xL2Snm4AZO+dYjSynfJiL4x7trEy80MImLftknBWT/qI0z7ox4Q7VNL3te6A==";var G=B;var he=class{constructor(e){this.route=[];this.instructions=[];this.forwardCum=new Float64Array(0);this.reverseCum=new Float64Array(0);this.snappedCoord=null;this.rawGpsCoord=null;this.bearing=0;this.rawGpsBearing=null;this.routeIndex=0;this.maxProgressIdx=0;this.prevSegment=null;this.distanceToRoute=0;this.corridor=25;this.prevDistToNext=null;this.currentSpeed=0;this.lastGpsTimestamp=0;this.instrIdx=0;this.linearOffset=0;this.hasArrived=false;this.listeners=new Map;this.options={licenseKey:e?.licenseKey??"",isDev:e?.isDev??false,isCircuit:e?.isCircuit??false,kalmanQ:e?.kalmanQ??1e-5,kalmanRBase:e?.kalmanRBase??5e-5,baseCorridorMeters:e?.baseCorridorMeters??25,maxCorridorMeters:e?.maxCorridorMeters??60,arrivalThresholdMeters:e?.arrivalThresholdMeters??30,bearingSmoothingAlpha:e?.bearingSmoothingAlpha??.3},this.kalman=new M(this.options.kalmanQ,this.options.kalmanRBase),this.snapper=new T({useBearingPenalty:true,useInertiaPenalty:true}),this.deadReckoning=new D,this.deviationDetector=new N,this.lifecycleFSM=new w,this.offRouteFSM=new F,this.license=new G(this.options.licenseKey,this.options.isDev);}has(e){let t=this.license.hasFeature(e);return t||this.license.warnOnce(e),t}setRoute(e,t){this.route=e,this.instructions=t??[],this.forwardCum=K(e),this.reverseCum=j(e),this.routeIndex=0,this.maxProgressIdx=0,this.prevSegment=null,this.instrIdx=0,this.linearOffset=0,this.hasArrived=false,this.snappedCoord=null,this.prevDistToNext=null,this.lifecycleFSM.dispatch({type:"ROUTE_LOADED"}),this.emit("routeLoaded",{routeLength:e.length});}startNavigation(){this.lifecycleFSM.dispatch({type:"USER_START_NAVIGATION"}),this.emit("navigationStarted");}update(e){if(this.rawGpsCoord=e.coord,this.rawGpsBearing=e.bearing,this.currentSpeed=e.speed,this.lastGpsTimestamp=e.timestamp,this.route.length<2)return this.getState();let[t,n]=this.kalman.update(e.coord[0],e.coord[1],e.accuracy??10,e.timestamp),i=[t,n],r=Math.max(0,this.routeIndex-2),o=this.snapper.snap(i,this.route,r,25,this.options.isCircuit,this.maxProgressIdx,this.routeIndex,e.bearing,this.prevSegment);this.corridor=Z(e.speed,e.accuracy,this.options.baseCorridorMeters,this.options.maxCorridorMeters);let c=te(this.routeIndex,o.idx,this.route,o.dist,80);if(this.prevSegment=this.routeIndex,this.routeIndex=c,this.maxProgressIdx=Math.max(this.maxProgressIdx,c),this.snappedCoord=o.proj,this.distanceToRoute=o.dist,this.forwardCum.length>c){let a=this.forwardCum[c]??0;this.linearOffset=oe(this.linearOffset,a,.15);}if(this.has(E.ADVANCED_BEARING)?this.bearing=ee(this.bearing,this.route,this.routeIndex,e.speed,e.bearing,this.options.bearingSmoothingAlpha):e.bearing!==null&&(this.bearing=e.bearing),this.has(E.COMPOUND_DEVIATION)&&(this.offRouteFSM.state==="ON_ROUTE"||this.offRouteFSM.state==="DEVIATING")){let a=Ae(i,this.route,r,25),u=this.deviationDetector.evaluate(i,e.bearing,e.speed,a,this.route,this.corridor,this.prevDistToNext);if(a.segmentIndex<this.route.length-1){let d=this.route[Math.min(a.segmentIndex+2,this.route.length-1)];this.prevDistToNext=m(i[1],i[0],d[1],d[0]);}u.confirmed?this.offRouteFSM.state==="ON_ROUTE"?this.offRouteFSM.dispatch({type:"DEVIATION_SUSPECTED",deviationSegmentIdx:a.segmentIndex}):this.offRouteFSM.isDeviationConfirmed()&&(this.offRouteFSM.dispatch({type:"DEVIATION_CONFIRMED",deviationSegmentIdx:a.segmentIndex}),this.emit("deviation",{anchorIndex:a.segmentIndex,ghostCoord:o.proj})):this.offRouteFSM.state==="DEVIATING"&&this.offRouteFSM.dispatch({type:"RESUME"});}else this.has(E.COMPOUND_DEVIATION)||(o.dist>this.corridor&&this.offRouteFSM.state==="ON_ROUTE"?(this.offRouteFSM.dispatch({type:"DEVIATION_SUSPECTED",deviationSegmentIdx:o.idx}),this.emit("deviation",{anchorIndex:o.idx,ghostCoord:o.proj})):o.dist<=this.corridor&&this.offRouteFSM.state!=="ON_ROUTE"&&this.offRouteFSM.dispatch({type:"RESUME"}));if(this.lifecycleFSM.state==="APPROACHING_ROUTE"&&o.dist<this.corridor&&this.lifecycleFSM.dispatch({type:"ROUTE_JOINED",data:{index:o.idx}}),this.instructions.length>0){let a=ne(this.routeIndex,this.instructions,this.instrIdx,this.route,i,e.bearing,this.linearOffset,e.speed);a.nextIdx!==this.instrIdx&&(this.instrIdx=a.nextIdx),a.wasTriggered&&a.instruction&&(this.emit("instruction",a.instruction),this.instrIdx=a.nextIdx+1);}if(!this.hasArrived&&this.route.length>0){let a=this.route[this.route.length-1],u=m(i[1],i[0],a[1],a[0]),d=this.options.isCircuit?this.routeIndex>=this.route.length*.8:true;u<this.options.arrivalThresholdMeters&&d&&(this.hasArrived=true,this.lifecycleFSM.dispatch({type:"NAVIGATION_FINISHED"}),this.emit("arrival"));}return this.has(E.DEAD_RECKONING)&&this.deadReckoning.setAnchor(o.proj,o.idx,e.timestamp),this.getState()}tick(e){if(this.has(E.DEAD_RECKONING)){let t=this.deadReckoning.predict(this.currentSpeed,this.bearing,e);t.isActive&&(this.snappedCoord=t.predictedCoord);}if(this.lastGpsTimestamp>0){let t=(e-this.lastGpsTimestamp)/1e3;t>0&&t<10&&(this.linearOffset=re(this.linearOffset,this.currentSpeed,t));}return this.getState()}getState(){let e=this.route.length>0&&this.reverseCum.length>this.routeIndex?this.reverseCum[this.routeIndex]??null:null,t=this.instrIdx<this.instructions.length?(()=>{let i=this.instructions[this.instrIdx];return i.cumulativeOffset!==void 0?Math.max(0,i.cumulativeOffset-this.linearOffset):null})():null,n=this.lastGpsTimestamp>0?Date.now()-this.lastGpsTimestamp:0;return {snappedCoord:this.snappedCoord,rawGpsCoord:this.rawGpsCoord,bearing:this.bearing,rawGpsBearing:this.rawGpsBearing,routeIndex:this.routeIndex,distanceToRoute:this.distanceToRoute,corridor:this.corridor,isOffRoute:this.offRouteFSM.state!=="ON_ROUTE"&&this.offRouteFSM.state!=="DEVIATING",progressIndex:this.routeIndex,hasArrived:this.hasArrived,offRouteState:this.offRouteFSM.state,lifecycleState:this.lifecycleFSM.state,distanceToDestination:e,distanceToNextInstruction:t,nextInstructionIndex:this.instrIdx<this.instructions.length?this.instrIdx:null,currentSpeed:this.currentSpeed,isGpsStale:n>3e3,gpsAgeMs:n,licenseStatus:this.license.getStatus()}}destroy(){this.listeners.clear(),this.route=[],this.instructions=[],this.kalman.reset(),this.deadReckoning.clearAnchor(),this.lifecycleFSM.reset(),this.offRouteFSM.reset();}on(e,t){this.listeners.has(e)||this.listeners.set(e,[]),this.listeners.get(e).push(t);}off(e,t){let n=this.listeners.get(e);if(n){let i=n.indexOf(t);i>=0&&n.splice(i,1);}}emit(e,...t){let n=this.listeners.get(e);if(n)for(let i of n)i(...t);}getLicenseManager(){return this.license}getRoute(){return this.route}getInstructions(){return this.instructions}getOffRouteFSM(){return this.offRouteFSM}getLifecycleFSM(){return this.lifecycleFSM}};
7
+ </gpx>`}fromGeoJSON(e){if(this.waypoints=[],e.type==="FeatureCollection"&&e.features){for(let t of e.features)if(t.geometry.type==="Point"){let n=t.geometry.coordinates;this.addWaypoint(n);}else if(t.geometry.type==="LineString"){let n=t.geometry.coordinates;for(let i of n)this.addWaypoint(i);}}}toGeoJSON(){let e=this.waypoints.map((n,i)=>({type:"Feature",geometry:{type:"Point",coordinates:n.coord},properties:{id:n.id,order:i,name:n.options?.name??null,silent:n.options?.silent??false}})),t={type:"Feature",geometry:{type:"LineString",coordinates:this.waypoints.map(n=>n.coord)},properties:{type:"route"}};return {type:"FeatureCollection",features:[...e,t]}}clear(){this.waypoints=[];}};var ae=class{constructor(e){this.config={baseUrl:e.baseUrl.replace(/\/$/,""),profile:e.profile??"driving",maxWaypointsPerChunk:e.maxWaypointsPerChunk??100};}async getRoute(e,t){if(e.length<2)throw new Error("[OSRM] At least 2 waypoints required");let n=t?.profile??this.config.profile;return e.length<=this.config.maxWaypointsPerChunk?this.fetchRoute(e,n,t):this.fetchChunked(e,n,t)}async fetchRoute(e,t,n){let i=e.map(d=>`${d[0]},${d[1]}`).join(";"),r=n?.steps!==false?"true":"false",o=`${this.config.baseUrl}/route/v1/${t}/${i}?overview=full&geometries=geojson&steps=${r}&annotations=false`,c=await fetch(o);if(!c.ok)throw new Error(`[OSRM] HTTP ${c.status}: ${c.statusText}`);let a=await c.json();if(a.code!=="Ok"||!a.routes?.length)throw new Error(`[OSRM] No route found: ${a.code}`);let u=a.routes[0];return {geometry:u.geometry.coordinates,distance:u.distance,duration:u.duration,legs:(u.legs??[]).map(d=>({distance:d.distance,duration:d.duration,steps:(d.steps??[]).map(l=>({distance:l.distance,duration:l.duration,geometry:l.geometry?.coordinates??[],maneuver:{type:l.maneuver?.type??"unknown",modifier:l.maneuver?.modifier,location:l.maneuver?.location??[0,0],bearingBefore:l.maneuver?.bearing_before??0,bearingAfter:l.maneuver?.bearing_after??0,instruction:l.name??void 0},name:l.name}))})),raw:a}}async fetchChunked(e,t,n){let i=this.config.maxWaypointsPerChunk,r=[];for(let l=0;l<e.length;l+=i-1){let p=e.slice(l,l+i);if(p.length<2||(r.push(p),l+i>=e.length))break}let o=await Promise.all(r.map(l=>this.fetchRoute(l,t,n))),c=[],a=0,u=0,d=[];for(let l=0;l<o.length;l++){let p=o[l];c.push(...l===0?p.geometry:p.geometry.slice(1)),a+=p.distance,u+=p.duration,d.push(...p.legs);}return {geometry:c,distance:a,duration:u,legs:d,raw:o.map(l=>l.raw)}}};var ce=class{constructor(e){this.config={baseUrl:e.baseUrl.replace(/\/$/,""),costing:e.costing??"auto",apiKey:e.apiKey??"",maxWaypointsPerChunk:e.maxWaypointsPerChunk??50};}async getRoute(e,t){if(e.length<2)throw new Error("[Valhalla] At least 2 waypoints required");let n=this.profileToCosting(t?.profile);return e.length<=this.config.maxWaypointsPerChunk?this.fetchRoute(e,n,t):this.fetchChunked(e,n,t)}profileToCosting(e){switch(e){case "cycling":return "bicycle";case "walking":return "pedestrian";default:return this.config.costing}}async fetchRoute(e,t,n){let i=e.map(p=>({lon:p[0],lat:p[1],type:"break"})),r=JSON.stringify({locations:i,costing:t,directions_options:{units:"kilometers"}}),o={"Content-Type":"application/json"};this.config.apiKey&&(o.api_key=this.config.apiKey);let c=await fetch(`${this.config.baseUrl}/route`,{method:"POST",headers:o,body:r});if(!c.ok){let p=await c.text();throw new Error(`[Valhalla] HTTP ${c.status}: ${p}`)}let a=await c.json(),u=this.decodePolyline(a.trip?.legs?.[0]?.shape??""),d=a.trip?.summary??{},l=(a.trip?.legs??[]).map(p=>({distance:(p.summary?.length??0)*1e3,duration:p.summary?.time??0,steps:(p.maneuvers??[]).map(g=>({distance:(g.length??0)*1e3,duration:g.time??0,geometry:[],maneuver:{type:g.type?.toString()??"unknown",modifier:void 0,location:e[0],bearingBefore:g.begin_heading??0,bearingAfter:g.end_heading??0,instruction:g.instruction},name:g.street_names?.[0]}))}));return {geometry:u,distance:(d.length??0)*1e3,duration:d.time??0,legs:l,raw:a}}decodePolyline(e,t=6){let n=Math.pow(10,t),i=[],r=0,o=0,c=0;for(;r<e.length;){let a=0,u=0,d;do d=e.charCodeAt(r++)-63,u|=(d&31)<<a,a+=5;while(d>=32);let l=(u&1)!==0?~(u>>1):u>>1;o+=l,a=0,u=0;do d=e.charCodeAt(r++)-63,u|=(d&31)<<a,a+=5;while(d>=32);let p=(u&1)!==0?~(u>>1):u>>1;c+=p,i.push([c/n,o/n]);}return i}async fetchChunked(e,t,n){let i=this.config.maxWaypointsPerChunk,r=[];for(let l=0;l<e.length;l+=i-1){let p=e.slice(l,l+i);if(p.length<2||(r.push(p),l+i>=e.length))break}let o=await Promise.all(r.map(l=>this.fetchRoute(l,t,n))),c=[],a=0,u=0,d=[];for(let l=0;l<o.length;l++){let p=o[l];c.push(...l===0?p.geometry:p.geometry.slice(1)),a+=p.distance,u+=p.duration,d.push(...p.legs);}return {geometry:c,distance:a,duration:u,legs:d,raw:o.map(l=>l.raw)}}};var Ze="https://api.openrouteservice.org/v2/directions";var le=class{constructor(e){this.config={apiKey:e.apiKey,profile:e.profile??"driving-car",baseUrl:e.baseUrl??Ze,maxWaypointsPerChunk:e.maxWaypointsPerChunk??50};}async getRoute(e,t){if(e.length<2)throw new Error("[ORS] At least 2 waypoints required");let n=this.profileFromOptions(t);return e.length<=this.config.maxWaypointsPerChunk?this.fetchRoute(e,n,t):this.fetchChunked(e,n,t)}profileFromOptions(e){switch(e?.profile){case "cycling":return "cycling-regular";case "walking":return "foot-walking";default:return this.config.profile}}async fetchRoute(e,t,n){let i=JSON.stringify({coordinates:e,instructions:n?.steps!==false,language:n?.language??"en"}),r=await fetch(`${this.config.baseUrl}/${t}/geojson`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:this.config.apiKey},body:i});if(!r.ok){let l=await r.text();throw new Error(`[ORS] HTTP ${r.status}: ${l}`)}let o=await r.json(),c=o.features?.[0];if(!c)throw new Error("[ORS] No route returned");let a=c.properties?.summary??{},d=(c.properties?.segments??[]).map(l=>({distance:l.distance??0,duration:l.duration??0,steps:(l.steps??[]).map(p=>({distance:p.distance??0,duration:p.duration??0,geometry:[],maneuver:{type:p.type?.toString()??"unknown",modifier:void 0,location:e[0],bearingBefore:0,bearingAfter:0,instruction:p.instruction},name:p.name}))}));return {geometry:c.geometry?.coordinates??[],distance:a.distance??0,duration:a.duration??0,legs:d,raw:o}}async fetchChunked(e,t,n){let i=this.config.maxWaypointsPerChunk,r=[];for(let l=0;l<e.length;l+=i-1){let p=e.slice(l,l+i);if(p.length<2||(r.push(p),l+i>=e.length))break}let o=await Promise.all(r.map(l=>this.fetchRoute(l,t,n))),c=[],a=0,u=0,d=[];for(let l=0;l<o.length;l++){let p=o[l];c.push(...l===0?p.geometry:p.geometry.slice(1)),a+=p.distance,u+=p.duration,d.push(...p.legs);}return {geometry:c,distance:a,duration:u,legs:d,raw:o.map(l=>l.raw)}}};var et={earlyTriggerMeters:120,lateTriggerMeters:20,reannounceMeters:300},ue=class{constructor(e){this.instructions=[];this.triggeredIds=new Set;this.reannouncedIds=new Set;this.options={...et,...e};}setInstructions(e){this.instructions=[...e],this.triggeredIds.clear(),this.reannouncedIds.clear();}update(e){let{distanceToNextInstruction:t,nextInstructionIndex:n}=e;if(n===null||t===null||n>=this.instructions.length)return null;let i=this.instructions[n],r=this.instrId(n),o=t;if(this.triggeredIds.has(r))return this.options.reannounceMeters>0&&!this.reannouncedIds.has(r)&&o<=this.options.reannounceMeters&&o>this.options.earlyTriggerMeters?(this.reannouncedIds.add(r),this.makeCue(i,o,"low")):null;if(o>this.options.earlyTriggerMeters)return null;if(o<-this.options.lateTriggerMeters)return this.triggeredIds.add(r),null;this.triggeredIds.add(r);let c=o<30?"urgent":o<60?"high":"normal";return this.makeCue(i,o,c)}onInstruction(e,t){let n=t.distanceToNextInstruction??0;return this.makeCue(e,n,"high")}reset(){this.triggeredIds.clear(),this.reannouncedIds.clear();}instrId(e){let t=this.instructions[e];return `${e}_${t?.geometryIndex??0}`}makeCue(e,t,n){return {instruction:e,text:e.text??e.instruction??"",distanceMetres:Math.max(0,t),priority:n,triggerId:`${e.geometryIndex}_${t.toFixed(0)}`}}};var tt={speedWindowSize:6,minimumSpeedMs:.5,fallbackSpeedMs:8.33},de=class{constructor(e){this.speedSamples=[];this.options={...tt,...e},this.lastReliableSpeed=this.options.fallbackSpeedMs;}update(e){let t=e.distanceToDestination??0,n=e.currentSpeed??0;this.speedSamples.push(n),this.speedSamples.length>this.options.speedWindowSize&&this.speedSamples.shift();let i=this.rollingAverage();i>=this.options.minimumSpeedMs&&(this.lastReliableSpeed=i);let r=i>=this.options.minimumSpeedMs?i:this.lastReliableSpeed,o=this.speedSamples.length>=Math.ceil(this.options.speedWindowSize/2)&&r>0&&t>0,c=r>0?t/r:0;return {etaSeconds:Math.round(c),etaMs:Date.now()+c*1e3,distanceRemainingM:t,averageSpeedMs:i,isReliable:o}}reset(){this.speedSamples=[],this.lastReliableSpeed=this.options.fallbackSpeedMs;}rollingAverage(){return this.speedSamples.length===0?0:this.speedSamples.reduce((t,n)=>t+n,0)/this.speedSamples.length}};var nt={dwellThresholdMs:1e4},pe=class{constructor(e){this.fences=new Map;this.states=new Map;this.options={...nt,...e};}addCircle(e,t,n,i){return this.fences.set(e,{kind:"circle",id:e,center:t,radiusM:n,meta:i}),this.states.set(e,{inside:false,enteredAtMs:null}),e}addPolygon(e,t,n){if(t.length<3)throw new Error("[Geofencing] Polygon requires at least 3 coordinates");return this.fences.set(e,{kind:"polygon",id:e,coords:t,meta:n}),this.states.set(e,{inside:false,enteredAtMs:null}),e}remove(e){this.fences.delete(e),this.states.delete(e);}clear(){this.fences.clear(),this.states.clear();}listIds(){return Array.from(this.fences.keys())}update(e){if(!e)return [];let t=[],n=Date.now();for(let[i,r]of this.fences){let o=this.states.get(i),c=this.contains(r,e);if(!o.inside&&c)o.inside=true,o.enteredAtMs=n,t.push({id:i,type:"enter",coord:e,meta:r.meta});else if(o.inside&&!c){o.inside=false;let a=o.enteredAtMs?n-o.enteredAtMs:0;o.enteredAtMs=null,t.push({id:i,type:"exit",coord:e,dwellMs:a,meta:r.meta});}else if(o.inside&&o.enteredAtMs!==null){let a=n-o.enteredAtMs;a>=this.options.dwellThresholdMs&&(t.push({id:i,type:"dwell",coord:e,dwellMs:a,meta:r.meta}),o.enteredAtMs=n);}}return t}isInside(e,t){let n=this.fences.get(e);return n?this.contains(n,t):false}contains(e,t){return e.kind==="circle"?m(t[1],t[0],e.center[1],e.center[0])<=e.radiusM:this.pointInPolygon(t,e.coords)}pointInPolygon(e,t){let[n,i]=e,r=false,o=t.length;for(let c=0,a=o-1;c<o;a=c++){let[u,d]=t[c],[l,p]=t[a];d>i!=p>i&&n<(l-u)*(i-d)/(p-d)+u&&(r=!r);}return r}};var E={GPS_SMOOTHING:"gps_smoothing",BASIC_SNAPPING:"basic_snapping",ADVANCED_SNAPPING:"advanced_snapping",DEAD_RECKONING:"dead_reckoning",ROUTE_OFFSET:"route_offset",PROGRESS_TRACKING:"progress_tracking",ADVANCED_BEARING:"advanced_bearing",COMPOUND_DEVIATION:"compound_deviation",PARALLEL_ROAD_RESOLUTION:"parallel_road_resolution",U_TURN_PROTECTION:"u_turn_protection",VOICE_TRIGGERS:"voice_triggers",ETA:"eta",GEOFENCING:"geofencing",NAV_AI:"nav_ai"};var B=class B{constructor(e,t){this.status="MISSING";this.payload=null;this.enabledFeatures=new Set;this.warnedFeatures=new Set;this.isDev=false;this.isDev=t??false,e&&this.validate(e);}async validate(e){try{if(this.status==="VALID"&&this.payload?.bid==="dev.bypass")return "VALID";let[t,n]=e.split(".");if(!t||!n)return this.status="INVALID",this.status;let i=atob(t);if(this.payload=JSON.parse(i),!this.payload||this.payload.exp<Date.now())return this.status="EXPIRED",this.status;if(!await this.verifySignature(t,n))return this.status="INVALID",this.status;this.status="VALID",this.extractFeatures(),console.log(`\u{1F511} [NavCore] License validation complete. Plan: ${this.isProEnabled()?"pro":"core"}`);}catch{this.status="INVALID";}return this.status}extractFeatures(){if(this.enabledFeatures.clear(),!!this.payload){if(Array.isArray(this.payload.f))for(let e of this.payload.f)this.enabledFeatures.add(e);this.payload.t==="pro"&&this.enabledFeatures.add("*");}}hasFeature(e){return this.isDev?true:this.status!=="VALID"?false:this.enabledFeatures.has("*")?true:this.enabledFeatures.has(e)}warnOnce(e){this.warnedFeatures.has(e)||(console.warn(`[NavCore] Feature '${e}' requires a Pro license`),this.warnedFeatures.add(e));}isProEnabled(){return this.hasFeature("*")}isAIEnabled(){return this.hasFeature(E.NAV_AI)}getStatus(){return this.status}getPayload(){return this.payload}getEnabledFeatures(){return Array.from(this.enabledFeatures)}async verifySignature(e,t){try{if(typeof crypto>"u"||!crypto.subtle)return !1;let i=new TextEncoder().encode(e),r=Uint8Array.from(atob(t),a=>a.charCodeAt(0)),o=Uint8Array.from(atob(B.PUBLIC_KEY_SPKI),a=>a.charCodeAt(0)),c=await crypto.subtle.importKey("spki",o,{name:"ECDSA",namedCurve:"P-256"},!0,["verify"]);return await crypto.subtle.verify({name:"ECDSA",hash:{name:"SHA-256"}},c,r,i)}catch{return false}}};B.PUBLIC_KEY_SPKI="MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEk6onJmJkWtRFusiSS69dcdONX4xL2Snm4AZO+dYjSynfJiL4x7trEy80MImLftknBWT/qI0z7ox4Q7VNL3te6A==";var G=B;var he=class{constructor(e){this.route=[];this.instructions=[];this.forwardCum=new Float64Array(0);this.reverseCum=new Float64Array(0);this.snappedCoord=null;this.rawGpsCoord=null;this.bearing=0;this.rawGpsBearing=null;this.routeIndex=0;this.maxProgressIdx=0;this.prevSegment=null;this.distanceToRoute=0;this.corridor=25;this.prevDistToNext=null;this.currentSpeed=0;this.lastGpsTimestamp=0;this.instrIdx=0;this.linearOffset=0;this.hasArrived=false;this.listeners=new Map;this.options={licenseKey:e?.licenseKey??"",isDev:e?.isDev??false,isCircuit:e?.isCircuit??false,kalmanQ:e?.kalmanQ??1e-5,kalmanRBase:e?.kalmanRBase??5e-5,baseCorridorMeters:e?.baseCorridorMeters??25,maxCorridorMeters:e?.maxCorridorMeters??60,arrivalThresholdMeters:e?.arrivalThresholdMeters??30,bearingSmoothingAlpha:e?.bearingSmoothingAlpha??.3},this.kalman=new M(this.options.kalmanQ,this.options.kalmanRBase),this.snapper=new T({useBearingPenalty:true,useInertiaPenalty:true}),this.deadReckoning=new D,this.deviationDetector=new N,this.lifecycleFSM=new w,this.offRouteFSM=new F,this.license=new G(this.options.licenseKey,this.options.isDev);}has(e){let t=this.license.hasFeature(e);return t||this.license.warnOnce(e),t}setRoute(e,t){this.route=e,this.instructions=t??[],this.forwardCum=K(e),this.reverseCum=j(e),this.routeIndex=0,this.maxProgressIdx=0,this.prevSegment=null,this.instrIdx=0,this.linearOffset=0,this.hasArrived=false,this.snappedCoord=null,this.prevDistToNext=null,this.lifecycleFSM.dispatch({type:"ROUTE_LOADED"}),this.emit("routeLoaded",{routeLength:e.length});}startNavigation(){this.lifecycleFSM.dispatch({type:"USER_START_NAVIGATION"}),this.emit("navigationStarted");}update(e){if(this.rawGpsCoord=e.coord,this.rawGpsBearing=e.bearing,this.currentSpeed=e.speed,this.lastGpsTimestamp=e.timestamp,this.route.length<2)return this.getState();let[t,n]=this.kalman.update(e.coord[0],e.coord[1],e.accuracy??10,e.timestamp),i=[t,n],r=Math.max(0,this.routeIndex-2),o=this.snapper.snap(i,this.route,r,25,this.options.isCircuit,this.maxProgressIdx,this.routeIndex,e.bearing,this.prevSegment);this.corridor=Z(e.speed,e.accuracy,this.options.baseCorridorMeters,this.options.maxCorridorMeters);let c=te(this.routeIndex,o.idx,this.route,o.dist,80);if(this.prevSegment=this.routeIndex,this.routeIndex=c,this.maxProgressIdx=Math.max(this.maxProgressIdx,c),this.snappedCoord=o.proj,this.distanceToRoute=o.dist,this.forwardCum.length>c){let a=this.forwardCum[c]??0;this.linearOffset=oe(this.linearOffset,a,.15);}if(this.has(E.ADVANCED_BEARING)?this.bearing=ee(this.bearing,this.route,this.routeIndex,e.speed,e.bearing,this.options.bearingSmoothingAlpha):e.bearing!==null&&(this.bearing=e.bearing),this.has(E.COMPOUND_DEVIATION)&&(this.offRouteFSM.state==="ON_ROUTE"||this.offRouteFSM.state==="DEVIATING")){let a=Ae(i,this.route,r,25),u=this.deviationDetector.evaluate(i,e.bearing,e.speed,a,this.route,this.corridor,this.prevDistToNext);if(a.segmentIndex<this.route.length-1){let d=this.route[Math.min(a.segmentIndex+2,this.route.length-1)];this.prevDistToNext=m(i[1],i[0],d[1],d[0]);}u.confirmed?this.offRouteFSM.state==="ON_ROUTE"?this.offRouteFSM.dispatch({type:"DEVIATION_SUSPECTED",deviationSegmentIdx:a.segmentIndex}):this.offRouteFSM.isDeviationConfirmed()&&(this.offRouteFSM.dispatch({type:"DEVIATION_CONFIRMED",deviationSegmentIdx:a.segmentIndex}),this.emit("deviation",{anchorIndex:a.segmentIndex,ghostCoord:o.proj})):this.offRouteFSM.state==="DEVIATING"&&this.offRouteFSM.dispatch({type:"RESUME"});}else this.has(E.COMPOUND_DEVIATION)||(o.dist>this.corridor&&this.offRouteFSM.state==="ON_ROUTE"?(this.offRouteFSM.dispatch({type:"DEVIATION_SUSPECTED",deviationSegmentIdx:o.idx}),this.emit("deviation",{anchorIndex:o.idx,ghostCoord:o.proj})):o.dist<=this.corridor&&this.offRouteFSM.state!=="ON_ROUTE"&&this.offRouteFSM.dispatch({type:"RESUME"}));if(this.lifecycleFSM.state==="APPROACHING_ROUTE"&&o.dist<this.corridor&&this.lifecycleFSM.dispatch({type:"ROUTE_JOINED",data:{index:o.idx}}),this.instructions.length>0){let a=ne(this.routeIndex,this.instructions,this.instrIdx,this.route,i,e.bearing,this.linearOffset,e.speed);a.nextIdx!==this.instrIdx&&(this.instrIdx=a.nextIdx),a.wasTriggered&&a.instruction&&(this.emit("instruction",a.instruction),this.instrIdx=a.nextIdx+1);}if(!this.hasArrived&&this.route.length>0){let a=this.route[this.route.length-1],u=m(i[1],i[0],a[1],a[0]),d=this.options.isCircuit?this.routeIndex>=this.route.length*.8:true;u<this.options.arrivalThresholdMeters&&d&&(this.hasArrived=true,this.lifecycleFSM.dispatch({type:"NAVIGATION_FINISHED"}),this.emit("arrival"));}return this.has(E.DEAD_RECKONING)&&this.deadReckoning.setAnchor(o.proj,o.idx,e.timestamp),this.getState()}tick(e){if(this.has(E.DEAD_RECKONING)){let t=this.deadReckoning.predict(this.currentSpeed,this.bearing,e);t.isActive&&(this.snappedCoord=t.predictedCoord);}if(this.lastGpsTimestamp>0){let t=(e-this.lastGpsTimestamp)/1e3;t>0&&t<10&&(this.linearOffset=re(this.linearOffset,this.currentSpeed,t));}return this.getState()}getState(){let e=this.route.length>0&&this.reverseCum.length>this.routeIndex?this.reverseCum[this.routeIndex]??null:null,t=this.instrIdx<this.instructions.length?(()=>{let i=this.instructions[this.instrIdx];return i.cumulativeOffset!==void 0?Math.max(0,i.cumulativeOffset-this.linearOffset):null})():null,n=this.lastGpsTimestamp>0?Date.now()-this.lastGpsTimestamp:0;return {snappedCoord:this.snappedCoord,rawGpsCoord:this.rawGpsCoord,bearing:this.bearing,rawGpsBearing:this.rawGpsBearing,routeIndex:this.routeIndex,distanceToRoute:this.distanceToRoute,corridor:this.corridor,isOffRoute:this.offRouteFSM.state!=="ON_ROUTE"&&this.offRouteFSM.state!=="DEVIATING",progressIndex:this.routeIndex,hasArrived:this.hasArrived,offRouteState:this.offRouteFSM.state,lifecycleState:this.lifecycleFSM.state,distanceToDestination:e,distanceToNextInstruction:t,nextInstructionIndex:this.instrIdx<this.instructions.length?this.instrIdx:null,currentSpeed:this.currentSpeed,isGpsStale:n>3e3,gpsAgeMs:n,licenseStatus:this.license.getStatus()}}destroy(){this.listeners.clear(),this.route=[],this.instructions=[],this.kalman.reset(),this.deadReckoning.clearAnchor(),this.lifecycleFSM.reset(),this.offRouteFSM.reset();}on(e,t){this.listeners.has(e)||this.listeners.set(e,[]),this.listeners.get(e).push(t);}off(e,t){let n=this.listeners.get(e);if(n){let i=n.indexOf(t);i>=0&&n.splice(i,1);}}emit(e,...t){let n=this.listeners.get(e);if(n)for(let i of n)i(...t);}getLicenseManager(){return this.license}getRoute(){return this.route}getInstructions(){return this.instructions}getOffRouteFSM(){return this.offRouteFSM}getLifecycleFSM(){return this.lifecycleFSM}};
8
8
  export{ve as Constants,se as CustomRouteBuilder,D as DeadReckoningEngine,N as DeviationDetector,de as ETAEngine,E as FEATURES,pe as GeofencingEngine,ie as InstructionEditor,M as KalmanFilter2D,G as LicenseManager,he as NavCore,w as NavigationFSM,ae as OSRMDirectionsProvider,F as OffRouteFSM,le as OpenRouteServiceProvider,Q as ParallelRoadResolver,T as RouteSnapper,ce as ValhallaDirectionsProvider,ue as VoiceTriggerEngine,re as advancePredictor,ee as applyBearingSmoothing,oe as applyGpsCorrection,U as blendBearing,K as buildForwardCumulativeDistances,j as buildReverseCumulativeDistances,Z as calculateDynamicCorridor,Me as calculateRobustBearing,Oe as computeDeadReckoningOnRoute,V as destinationPoint,we as geoGetBearing,f as getBearingBetweenPoints,_e as getSpeedAdaptiveAlpha,m as haversineDistance,H as interpolatePoint,me as kalmanUpdate2D,y as normalizeAngle,ze as pointAtOffset,W as projectPointOnSegment,ne as resolveNextInstruction,te as validateProgressUpdate};
package/package.json CHANGED
@@ -1,72 +1,72 @@
1
- {
2
- "name": "@ingissa/navcore-core",
3
- "version": "1.0.0",
4
- "description": "NavCore navigation engine — zero runtime dependencies. Framework-agnostic. Works in browsers, React Native, Node.js, and CI/CD.",
5
- "author": "Ingissa <contact@navcore.dev>",
6
- "license": "MIT",
7
- "homepage": "https://navcore.dev",
8
- "repository": {
9
- "type": "git",
10
- "url": "https://github.com/ingissa/navcore-sdk"
11
- },
12
- "bugs": {
13
- "url": "https://github.com/ingissa/navcore-sdk/issues"
14
- },
15
- "keywords": [
16
- "navigation",
17
- "gps",
18
- "routing",
19
- "kalman",
20
- "dead-reckoning",
21
- "mapbox",
22
- "maplibre",
23
- "react-native",
24
- "geofencing",
25
- "eta",
26
- "off-route",
27
- "turn-by-turn"
28
- ],
29
- "main": "dist/index.cjs",
30
- "module": "dist/index.mjs",
31
- "types": "dist/index.d.ts",
32
- "exports": {
33
- ".": {
34
- "types": "./dist/index.d.ts",
35
- "import": "./dist/index.mjs",
36
- "require": "./dist/index.cjs"
37
- },
38
- "./core": {
39
- "types": "./dist/core.d.ts",
40
- "import": "./dist/core.mjs",
41
- "require": "./dist/core.cjs"
42
- },
43
- "./pro": {
44
- "types": "./dist/pro.d.ts",
45
- "import": "./dist/pro.mjs",
46
- "require": "./dist/pro.cjs"
47
- }
48
- },
49
- "files": [
50
- "dist"
51
- ],
52
- "sideEffects": false,
53
- "publishConfig": {
54
- "access": "public"
55
- },
56
- "engines": {
57
- "node": ">=16"
58
- },
59
- "scripts": {
60
- "build": "tsup",
61
- "build:types": "tsc -p tsconfig.build.json --emitDeclarationOnly",
62
- "prepublishOnly": "npm run build",
63
- "test": "vitest run",
64
- "test:watch": "vitest",
65
- "lint": "tsc --noEmit"
66
- },
67
- "devDependencies": {
68
- "tsup": "^8.5.1",
69
- "typescript": "^5.4.0",
70
- "vitest": "^3.1.0"
71
- }
72
- }
1
+ {
2
+ "name": "@ingissa/navcore-core",
3
+ "version": "1.0.2",
4
+ "description": "NavCore navigation engine — zero runtime dependencies. Framework-agnostic. Works in browsers, React Native, Node.js, and CI/CD.",
5
+ "author": "Ingissa <8abnav@gmail.com>",
6
+ "license": "Commercial",
7
+ "homepage": "https://navcore.dev",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/ingissa/navcore-sdk.git"
11
+ },
12
+ "bugs": {
13
+ "url": "https://github.com/ingissa/navcore-sdk/issues"
14
+ },
15
+ "keywords": [
16
+ "navigation",
17
+ "gps",
18
+ "routing",
19
+ "kalman",
20
+ "dead-reckoning",
21
+ "mapbox",
22
+ "maplibre",
23
+ "react-native",
24
+ "geofencing",
25
+ "eta",
26
+ "off-route",
27
+ "turn-by-turn"
28
+ ],
29
+ "main": "dist/index.cjs",
30
+ "module": "dist/index.mjs",
31
+ "types": "dist/index.d.ts",
32
+ "exports": {
33
+ ".": {
34
+ "types": "./dist/index.d.ts",
35
+ "import": "./dist/index.mjs",
36
+ "require": "./dist/index.cjs"
37
+ },
38
+ "./core": {
39
+ "types": "./dist/core.d.ts",
40
+ "import": "./dist/core.mjs",
41
+ "require": "./dist/core.cjs"
42
+ },
43
+ "./pro": {
44
+ "types": "./dist/pro.d.ts",
45
+ "import": "./dist/pro.mjs",
46
+ "require": "./dist/pro.cjs"
47
+ }
48
+ },
49
+ "files": [
50
+ "dist"
51
+ ],
52
+ "sideEffects": false,
53
+ "publishConfig": {
54
+ "access": "public"
55
+ },
56
+ "engines": {
57
+ "node": ">=16"
58
+ },
59
+ "scripts": {
60
+ "build": "tsup",
61
+ "build:types": "tsc -p tsconfig.build.json --emitDeclarationOnly",
62
+ "prepublishOnly": "npm run build",
63
+ "test": "vitest run",
64
+ "test:watch": "vitest",
65
+ "lint": "tsc --noEmit"
66
+ },
67
+ "devDependencies": {
68
+ "tsup": "^8.5.1",
69
+ "typescript": "^5.4.0",
70
+ "vitest": "^3.1.0"
71
+ }
72
+ }