NavCore SDK Documentation

The navigation intelligence layer that plugs into your renderer and keeps Core and Pro behavior explicit.

Installation

Install the engine first. Add only the adapters, simulator, or provider package your app actually uses.

# Required engine
npm install @ingissa/navcore-core

# Optional packages
npm install @ingissa/navcore-mapbox        # Mapbox Directions provider
npm install @ingissa/navcore-maplibre      # MapLibre GL renderer adapter
npm install @ingissa/navcore-leaflet       # Leaflet renderer adapter
npm install @ingissa/navcore-google-maps   # Google Maps JS renderer adapter
npm install @ingissa/navcore-react-native  # React Native useNavCore hook
npm install @ingissa/navcore-headless      # Node.js / CI adapter
npm install @ingissa/navcore-simulator     # GPS simulation and replay
npm install @ingissa/navcore-types         # Shared ecosystem types
@ingissa/navcore-core1.0.4

The full NavCore orchestrator, Core subpath, Pro subpath, directions providers, and license manager.

@ingissa/navcore-mapbox1.0.4

Mapbox Directions provider for token-based routing and waypoint chunking workflows.

@ingissa/navcore-react-native1.0.4

React Native bindings with the useNavCore hook and NavCoreProvider lifecycle helpers.

@ingissa/navcore-simulator1.0.4

GPS route simulator with replay, noise, scenarios, fleet simulation, and dev/CI entrypoints.

@ingissa/navcore-maplibre1.0.0

Renderer adapter for MapLibre GL JS. Handles vehicle marker, route layer, and camera panning.

@ingissa/navcore-leaflet1.0.0

Renderer adapter for Leaflet.js with route drawing, marker updates, and camera following.

@ingissa/navcore-google-maps1.0.0

Renderer adapter for the Google Maps JavaScript API with marker and polyline support.

@ingissa/navcore-headless1.0.0

Headless renderer for Node.js and CI assertions without a browser or visual map.

@ingissa/navcore-types1.0.1

Shared TypeScript type definitions for SDK ecosystem integrations.

OSRM, Valhalla, and OpenRouteService providers are exported by @ingissa/navcore-core. Mapbox Directions is provided by @ingissa/navcore-mapbox.

Core vs Pro

TierImportIncludes
Core@ingissa/navcore-core/coreGeo utilities, KalmanFilter2D, RouteSnapper, core directions, CustomRouteBuilder, renderer types, LicenseManager
Pro@ingissa/navcore-core/proDeadReckoningEngine, DeviationDetector, ParallelRoadResolver, ETAEngine, VoiceTriggerEngine, GeofencingEngine, InstructionEditor
Full engine@ingissa/navcore-coreNavCore orchestrator. Core features work without a key; Pro features unlock from licenseKey at runtime.
There is no isDev constructor option in the current SDK. Use new NavCore() for Core behavior or new NavCore({ licenseKey }) for licensed Pro behavior (SDK v1.0.4).

Quick Start

Get a route, create the engine, then feed GPS updates.

import { NavCore } from '@ingissa/navcore-core';
import { OSRMDirectionsProvider } from '@ingissa/navcore-core/core';

const provider = new OSRMDirectionsProvider({
  baseUrl: 'http://router.project-osrm.org',
});

const route = await provider.getRoute([
  [2.3522, 48.8566],
  [2.3009, 48.8741],
]);

const engine = new NavCore({
  licenseKey: process.env.NAVCORE_LICENSE_KEY, // optional for Core-only use
});

engine.setRoute(route.geometry);
engine.startNavigation();

engine.on('instruction', (instruction) => console.log(instruction.text));
engine.on('deviation', ({ anchorIndex }) => console.log('Off route at', anchorIndex));
engine.on('arrival', () => console.log('Arrived'));

navigator.geolocation.watchPosition(({ coords }) => {
  const state = engine.update({
    coord: [coords.longitude, coords.latitude],
    accuracy: coords.accuracy,
    bearing: coords.heading,
    speed: coords.speed ?? 0,
    timestamp: Date.now(),
  });

  console.log(state.snappedCoord, state.distanceToDestination, state.licenseStatus);
});

Engine API

const engine = new NavCore(options?: NavCoreOptions);

engine.setRoute(route: Coordinate[], instructions?: NavInstruction[]): void
engine.startNavigation(): void
engine.update(gps: NavGpsUpdate): NavCoreState
engine.tick(nowMs: number): NavCoreState
engine.getState(): NavCoreState
engine.getRoute(): ReadonlyArray<Coordinate>
engine.getInstructions(): ReadonlyArray<NavInstruction>
engine.getLicenseManager(): LicenseManager
engine.getOffRouteFSM(): OffRouteFSM
engine.getLifecycleFSM(): NavigationFSM
engine.on(event, handler): void
engine.off(event, handler): void
engine.destroy(): void

State Snapshot

interface NavCoreState {
  snappedCoord: Coordinate | null;
  rawGpsCoord: Coordinate | null;
  bearing: number;
  rawGpsBearing: number | null;
  routeIndex: number;
  distanceToRoute: number;
  corridor: number;
  isOffRoute: boolean;
  progressIndex: number;
  hasArrived: boolean;
  offRouteState: string;
  lifecycleState: string;
  distanceToDestination: number | null;
  distanceToNextInstruction: number | null;
  nextInstructionIndex: number | null;
  currentSpeed: number;
  isGpsStale: boolean;
  gpsAgeMs: number;
  licenseStatus: 'VALID' | 'INVALID' | 'EXPIRED' | 'MISSING';
}

Renderer Adapters

Renderer adapters consume NavCoreState. The engine does not emit an update event; call adapter methods after engine.update() or from your app state loop.

import maplibregl from 'maplibre-gl';
import { NavCore } from '@ingissa/navcore-core';
import { MapLibreAdapter } from '@ingissa/navcore-maplibre';

const engine = new NavCore({ licenseKey: process.env.NAVCORE_LICENSE_KEY });
const adapter = new MapLibreAdapter(map);

engine.setRoute(route.geometry, instructions);
adapter.drawRoute(route.geometry, { color: '#7c3aed', width: 5 });
engine.startNavigation();

function onGps(coords: GeolocationCoordinates) {
  const state = engine.update({
    coord: [coords.longitude, coords.latitude],
    accuracy: coords.accuracy,
    bearing: coords.heading,
    speed: coords.speed ?? 0,
    timestamp: Date.now(),
  });

  if (!state.snappedCoord) return;
  adapter.updateVehicle(state.snappedCoord, state.bearing, state);
  adapter.panCamera(state.snappedCoord, state.bearing, { zoom: 15, pitch: 45 });
}

Headless Testing

import { HeadlessAdapter } from '@ingissa/navcore-headless';

const adapter = new HeadlessAdapter();
adapter.drawRoute(route.geometry);

for (const gps of gpsTrace) {
  const state = engine.update(gps);
  if (state.snappedCoord) {
    adapter.updateVehicle(state.snappedCoord, state.bearing, state);
  }
}

adapter.assertArrived();
adapter.assertNeverOffRoute();
adapter.assertReached(route.geometry.at(-1)!, 30);

Directions Providers

ProviderPackageTypical use
OSRM@ingissa/navcore-core/coreSelf-hosted or public OSRM routing
Valhalla@ingissa/navcore-core/coreSelf-hosted Valhalla routing with costing models
OpenRouteService@ingissa/navcore-core/coreHosted ORS routing with API key
Mapbox Directions@ingissa/navcore-mapboxMapbox token-based routing and waypoint chunking
import {
  OSRMDirectionsProvider,
  ValhallaDirectionsProvider,
  OpenRouteServiceProvider,
} from '@ingissa/navcore-core/core';
import { MapboxDirectionsProvider } from '@ingissa/navcore-mapbox';

const osrm = new OSRMDirectionsProvider({ baseUrl: 'http://router.project-osrm.org' });
const valhalla = new ValhallaDirectionsProvider({ baseUrl: 'https://valhalla1.openstreetmap.de' });
const ors = new OpenRouteServiceProvider({ apiKey: process.env.ORS_API_KEY });
const mapbox = new MapboxDirectionsProvider({ accessToken: process.env.MAPBOX_TOKEN });

Pro Modules

Pro modules check licensing at runtime. Without a valid key, they return neutral or inactive results instead of breaking Core navigation.

ETA and Voice

import { ETAEngine, VoiceTriggerEngine } from '@ingissa/navcore-core/pro';

const eta = new ETAEngine({ speedWindowSize: 6 });
const voice = new VoiceTriggerEngine({ earlyTriggerMeters: 120, lateTriggerMeters: 10 });

voice.setInstructions(instructions);

const state = engine.update(gps);
const etaResult = eta.update(state);
const cue = voice.update(state);

if (etaResult.isReliable) {
  console.log(Math.ceil(etaResult.etaSeconds / 60), 'min');
}

if (cue) {
  speechSynthesis.speak(new SpeechSynthesisUtterance(cue.text));
}

Geofencing

import { GeofencingEngine } from '@ingissa/navcore-core/pro';

const geo = new GeofencingEngine({ dwellThresholdMs: 10_000 });

geo.addCircle('school-zone', 'School Zone', [2.3522, 48.8566], 50);
geo.addPolygon('exam-area', 'Exam Area', [
  [2.34, 48.85],
  [2.36, 48.85],
  [2.36, 48.87],
  [2.34, 48.87],
]);

const state = engine.update(gps);
if (state.snappedCoord) {
  const events = geo.update(state.snappedCoord);
  for (const event of events) {
    console.log(event.type, event.name, event.dwellMs);
  }
}

Custom Routes & Instructions

CustomRouteBuilder

import { CustomRouteBuilder } from '@ingissa/navcore-core/core';

const builder = new CustomRouteBuilder();
builder.addWaypoint([2.3522, 48.8566], { name: 'Start' });
builder.addWaypoint([2.3400, 48.8650], { name: 'Midpoint' });
builder.addWaypoint([2.3009, 48.8741], { name: 'End' });

const chunks = builder.chunk('OVERLAP_1', 25);
const results = await Promise.all(chunks.map((chunk) => provider.getRoute(chunk)));
const geometry = results.flatMap((result, index) =>
  index === 0 ? result.geometry : result.geometry.slice(1)
);

engine.setRoute(geometry);

InstructionEditor

import { InstructionEditor } from '@ingissa/navcore-core/pro';

const editor = new InstructionEditor();
editor
  .addDepart(geometry[0], 'Start navigation')
  .addTurn(geometry[50], 'Turn left onto Main St', 50)
  .addExamPoint(geometry[80], 'Check mirrors', 80, { severity: 'warning' })
  .addArrive(geometry[geometry.length - 1]);

editor.attachToCumulative(geometry);
editor.sort();

engine.setRoute(geometry, editor.toArray());

React Native

import { useNavCore } from '@ingissa/navcore-react-native';

const nav = useNavCore({
  gpsCoord,
  gpsBearing,
  gpsAccuracy,
  gpsTimestamp,
  speed,
  routeGeometry,
  instructions,
  isNavigating,
  options: {
    licenseKey: process.env.EXPO_PUBLIC_NAVCORE_LICENSE_KEY,
    isCircuit: false,
  },
  onDeviation: ({ anchorIndex }) => requestNewRoute(anchorIndex),
  onInstruction: (instruction) => speak(instruction.text),
  onArrival: () => showArrivalScreen(),
});

if (nav.snappedCoord) {
  mapRef.current?.animateCamera({
    center: {
      latitude: nav.snappedCoord[1],
      longitude: nav.snappedCoord[0],
    },
    heading: nav.bearing,
  });
}

Examples

The examples repository covers basic navigation, custom routes, instruction editing, renderer adapters, directions providers, voice, ETA, geofencing, parallel-road resolution, headless testing, and full Expo navigation screens.

Recommended first reads

  • 01 Basic Navigation: Core engine plus OSRM route loading.
  • 04 MapLibre Web: Browser rendering with MapLibre.
  • 08 ETA Engine: Licensed ETA computation from NavCoreState.
  • 11 Headless Testing: CI assertions without a map.
  • 12 Expo Complete Navigation: Mobile screens for multiple renderer stacks.
View NavCore Examples Repository

Ready to integrate?

Start with Core, then move to Pro when your app needs licensed navigation intelligence.