Skip to content

visionik/anywhere

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

14 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Anywhere — Unified Location Library

Get reliable position data from any source — device GPS, NMEA 0183, GDL-90, and more.

@visionik/anywhere is a lightweight TypeScript library that normalizes GPS/location data from multiple heterogeneous sources into a single, consistent Position interface. It supports automatic source prioritization, seamless fallback, and easy extension for new providers.

Perfect for aviation apps (EFBs, Stratux/ForeFlight-style tools), drone software, marine navigation, or any project that needs robust, reliable location from diverse hardware or OS APIs.


Architecture Overview

Anywhere is built around a source → manager → consumer pipeline. Each location provider implements a common LocationSource interface, and the LocationManager orchestrates them: selecting the highest-quality active source, falling back automatically when a source degrades, and emitting a single unified stream of normalized Position events to your application.

%%{init: {'theme': 'base', 'themeVariables': {'primaryTextColor': '#000000', 'secondaryTextColor': '#000000', 'tertiaryTextColor': '#000000', 'noteTextColor': '#000000', 'primaryColor': '#909090', 'secondaryColor': '#808080', 'tertiaryColor': '#707070', 'lineColor': '#404040', 'actorLineColor': '#404040', 'signalColor': '#404040', 'actorBkg': '#808080', 'actorTextColor': '#000000', 'noteBkgColor': '#909090', 'stateLabelColor': '#000000', 'compositeBackground': '#a0a0a0'}}}%%
flowchart LR
    subgraph Sources[" Location Sources "]
        A[DeviceLocationSource\nBrowser / Native OS]
        B[GDL90Source\nStratux / ADS-B]
        C[NMEASource\nSerial / UDP / BT]
        D[SimulatorSource\nTesting / Replay]
    end
    subgraph Manager[" LocationManager "]
        E[Priority & Fusion\nFallback Logic]
    end
    subgraph App[" Your Application "]
        F[Position Consumer\nonPosition handler]
    end
    A -->|Position| E
    B -->|Position| E
    C -->|Position| E
    D -->|Position| E
    E -->|Best Position| F
Loading

This design means you can add or remove sources at runtime without changing your application logic. Your app always receives the same Position shape, regardless of which hardware is currently active.


Core Concepts

The Position Interface

Every source in Anywhere emits a normalized Position object — regardless of whether the data originated from a browser's Geolocation API, a serial NMEA receiver, or a GDL-90 UDP broadcast. This eliminates format-specific handling in your application code.

The interface captures the most useful fields from aviation-grade GPS receivers:

  • Coordinates — WGS84 decimal degrees latitude/longitude and meters MSL altitude
  • Motion — ground speed (m/s) and true heading (degrees 0–360)
  • Fix quality — fix type (2D, 3D, DGPS, RTK), HDOP, and satellite count
  • Accuracy — horizontal and vertical accuracy estimates in meters
  • AHRS extensions — roll and pitch when available (e.g., from GDL-90 AHRS messages)
  • Source tag — identifies which provider produced the fix, useful for debugging or display
%%{init: {'theme': 'base', 'themeVariables': {'primaryTextColor': '#000000', 'secondaryTextColor': '#000000', 'tertiaryTextColor': '#000000', 'noteTextColor': '#000000', 'primaryColor': '#909090', 'secondaryColor': '#808080', 'tertiaryColor': '#707070', 'lineColor': '#404040', 'actorLineColor': '#404040', 'signalColor': '#404040', 'actorBkg': '#808080', 'actorTextColor': '#000000', 'noteBkgColor': '#909090', 'stateLabelColor': '#000000', 'compositeBackground': '#a0a0a0'}}}%%
classDiagram
    class Position {
        +number latitude
        +number longitude
        +number? altitude
        +number? speed
        +number? heading
        +Date timestamp
        +number? accuracy
        +number? verticalAccuracy
        +string source
        +number? satellites
        +number? hdop
        +string? fixType
        +number? roll
        +number? pitch
        +number? magneticVariation
    }
Loading
export interface Position {
  latitude: number;           // decimal degrees, WGS84
  longitude: number;          // decimal degrees, WGS84
  altitude?: number;          // meters (MSL or geometric)
  speed?: number;             // meters per second
  heading?: number;           // degrees true (0–360)
  timestamp: Date;            // UTC time of the fix
  accuracy?: number;          // horizontal accuracy in meters
  verticalAccuracy?: number;  // vertical accuracy in meters
  source: 'device' | 'nmea' | 'gdl90' | 'simulator' | string;
  satellites?: number;        // satellites in fix
  hdop?: number;              // horizontal dilution of precision
  fixType?: 'none' | '2d' | '3d' | 'dgps' | 'rtk' | string;
  // AHRS extensions (GDL-90 / ForeFlight)
  roll?: number;              // degrees
  pitch?: number;             // degrees
  magneticVariation?: number; // degrees east/west
}

The LocationSource Abstraction

All providers extend a common abstract base class. To add a new data source, you implement start() and stop(), call emitPosition() when you have data, and the manager handles the rest. The onStatus callback lets sources report connection health and fix quality in real time, enabling the manager to make informed fallback decisions.

%%{init: {'theme': 'base', 'themeVariables': {'primaryTextColor': '#000000', 'secondaryTextColor': '#000000', 'tertiaryTextColor': '#000000', 'noteTextColor': '#000000', 'primaryColor': '#909090', 'secondaryColor': '#808080', 'tertiaryColor': '#707070', 'lineColor': '#404040', 'actorLineColor': '#404040', 'signalColor': '#404040', 'actorBkg': '#808080', 'actorTextColor': '#000000', 'noteBkgColor': '#909090', 'stateLabelColor': '#000000', 'compositeBackground': '#a0a0a0'}}}%%
classDiagram
    class LocationSource {
        <<abstract>>
        +start() void
        +stop() void
        +onPosition(pos Position) callback
        +onError(err Error) callback
        +onStatus(status StatusEvent) callback
        #emitPosition(pos Position) void
    }
    class DeviceLocationSource {
        -enableHighAccuracy boolean
        -timeoutMs number
        -maximumAgeMs number
        +start() void
        +stop() void
    }
    class NMEASource {
        -type serial|udp|tcp|bt
        -port number|string
        -baudRate number
        +start() void
        +stop() void
    }
    class GDL90Source {
        -port number
        -bindAddress string
        -enableAHRS boolean
        +start() void
        +stop() void
    }
    class SimulatorSource {
        -route Position[]
        -intervalMs number
        +start() void
        +stop() void
    }
    LocationSource <|-- DeviceLocationSource
    LocationSource <|-- NMEASource
    LocationSource <|-- GDL90Source
    LocationSource <|-- SimulatorSource
Loading

The LocationManager

LocationManager is the primary API surface. You configure it with one or more sources and a priority order; it handles lifecycle management, source health monitoring, and seamless fallback. Your application only needs to listen for 'position' events.

const manager = new LocationManager({
  sources: [
    new DeviceLocationSource({ enableHighAccuracy: true }),
    new GDL90Source({ port: 4000 }),
    new NMEASource({ type: 'udp', port: 10110 }),
  ],
  priorityOrder: ['gdl90', 'nmea', 'device'],
  minUpdateIntervalMs: 200,
});

manager.on('position', (pos) => {
  console.log(`[${pos.source}] ${pos.latitude}, ${pos.longitude}`);
});

manager.on('sourceChange', (from, to) => {
  console.log(`Active source changed: ${from}${to}`);
});

manager.start();

Location Sources

1. Device Location Source

The device source wraps the platform's native location API, providing a uniform interface across all major deployment targets. Whether running as a web app in a browser, a native mobile app via Capacitor, or a desktop app via Electron or Tauri, the same DeviceLocationSource API works everywhere.

Platform Underlying API
Browser / PWA navigator.geolocation (W3C Geolocation API)
iOS & macOS CoreLocation via Capacitor, React Native, or Electron bridge
Android Fused Location Provider via Capacitor or native
Desktop (Electron / Tauri) CoreLocation / Geolocator / GeoClue depending on OS

The source supports both one-shot (getCurrentPosition) and continuous watch (watchPosition) modes, and reports accuracy degradation via onStatus so the manager can decide when to fall back to another source.

%%{init: {'theme': 'base', 'themeVariables': {'primaryTextColor': '#000000', 'secondaryTextColor': '#000000', 'tertiaryTextColor': '#000000', 'noteTextColor': '#000000', 'primaryColor': '#909090', 'secondaryColor': '#808080', 'tertiaryColor': '#707070', 'lineColor': '#404040', 'actorLineColor': '#404040', 'signalColor': '#404040', 'actorBkg': '#808080', 'actorTextColor': '#000000', 'noteBkgColor': '#909090', 'stateLabelColor': '#000000', 'compositeBackground': '#a0a0a0'}}}%%
flowchart TD
    A[DeviceLocationSource.start] --> B{Platform?}
    B -->|Browser| C[navigator.geolocation\n.watchPosition]
    B -->|iOS / macOS| D[CoreLocation\nCLLocationManager]
    B -->|Android| E[Fused Location\nProvider]
    B -->|Electron / Tauri| F[OS Geolocation API]
    C & D & E & F --> G[Normalize to Position]
    G --> H[emitPosition]
Loading

2. NMEA 0183 Source

NMEA 0183 is the universally supported serial protocol for GPS receivers — from budget USB dongles to panel-mounted avionics. The NMEASource supports multiple transport layers and parses all major fix-quality sentences.

Transport options:

  • Serial — USB / RS-232 via WebSerial (browser) or Node.js serialport
  • UDP / TCP — common for network-bridged receivers (e.g., GPSd, Stratux NMEA forwarding)
  • Bluetooth — via platform BLE/SPP APIs
  • File replay — for deterministic testing and simulation

Sentences parsed:

Sentence Content
$xxRMC Position, speed, course, UTC time, status
$xxGGA Position, altitude, fix quality, satellite count
$xxVTG True/magnetic track, ground speed
$xxGSA DOP values, fix type, active satellite IDs
$xxGLL Geographic position (lat/lon only)

The parser handles all standard talker ID prefixes (GP, GN, GL, GA), validates checksums, and assembles multi-sentence update cycles into a single coherent Position emission per fix epoch.

%%{init: {'theme': 'base', 'themeVariables': {'primaryTextColor': '#000000', 'secondaryTextColor': '#000000', 'tertiaryTextColor': '#000000', 'noteTextColor': '#000000', 'primaryColor': '#909090', 'secondaryColor': '#808080', 'tertiaryColor': '#707070', 'lineColor': '#404040', 'actorLineColor': '#404040', 'signalColor': '#404040', 'actorBkg': '#808080', 'actorTextColor': '#000000', 'noteBkgColor': '#909090', 'stateLabelColor': '#000000', 'compositeBackground': '#a0a0a0'}}}%%
flowchart TD
    A[Transport\nSerial / UDP / TCP / BT] --> B[Raw NMEA Byte Stream]
    B --> C[Line Buffer &\nSentence Splitter]
    C --> D{Checksum\nValid?}
    D -->|No| E[Discard / onError]
    D -->|Yes| F{Sentence Type?}
    F -->|RMC| G[Position, speed,\ncourse, time, status]
    F -->|GGA| H[Altitude, fix\nquality, satellites]
    F -->|VTG| I[True track,\nground speed]
    F -->|GSA| J[DOP values,\nfix type]
    F -->|GLL| K[Lat/lon]
    F -->|Other| L[Ignore]
    G & H & I & J & K --> M[Fix Accumulator\nmerge per epoch]
    M --> N[emitPosition]
Loading

3. GDL-90 Source

GDL-90 is a binary UDP protocol originally defined by the FAA and widely adopted in portable ADS-B receivers. It is the native protocol for Stratux, uAvionix SkyEcho, Appareo Stratus (open mode), and many DIY ADS-B builds. Beyond ownship GPS position, GDL-90 delivers traffic reports, FIS-B weather data, and attitude (AHRS) data via the ForeFlight extension.

Why GDL-90 matters for aviation: A Stratux or similar receiver connected over Wi-Fi broadcasts GDL-90 on UDP port 4000. Any EFB that speaks GDL-90 can receive traffic, weather, and precise GPS without cellular service or an external GPS module — the receiver handles everything.

Key messages decoded:

Message ID Description
0x00 Heartbeat GPS validity flags, UTC timestamp, status bits
0x0B Ownship Report Primary GPS position, geometric altitude, ground velocity
0x65 ForeFlight AHRS Roll and pitch (annotated onto the next ownship Position); heading decoded but not exposed (it is the aircraft's magnetic heading, not magnetic declination)

The source manages UDP socket binding, 0x7E frame delimiter detection, byte unstuffing (0x7D escape sequences), and CRC-16/CCITT validation before decoding any message payload.

%%{init: {'theme': 'base', 'themeVariables': {'primaryTextColor': '#000000', 'secondaryTextColor': '#000000', 'tertiaryTextColor': '#000000', 'noteTextColor': '#000000', 'primaryColor': '#909090', 'secondaryColor': '#808080', 'tertiaryColor': '#707070', 'lineColor': '#404040', 'actorLineColor': '#404040', 'signalColor': '#404040', 'actorBkg': '#808080', 'actorTextColor': '#000000', 'noteBkgColor': '#909090', 'stateLabelColor': '#000000', 'compositeBackground': '#a0a0a0'}}}%%
flowchart TD
    A[UDP Socket\nport 4000] --> B[Raw Datagram]
    B --> C[0x7E Frame\nDelimiter Detection]
    C --> D[Byte Unstuffing\n0x7D escape handling]
    D --> E{CRC-16\nValid?}
    E -->|No| F[Discard]
    E -->|Yes| G{Message ID?}
    G -->|0x00 Heartbeat| H[GPS validity flags\n& UTC timestamp]
    G -->|0x0B Ownship| I[Lat/lon, altitude,\nground velocity]
    G -->|0x65 AHRS| J[Roll, pitch,\nheading, yaw rate]
    G -->|Other| K[Ignore / Future use]
    H --> L[Update source\nhealth status]
    I --> M[Build Position\nfrom ownship data]
    J --> N[Annotate Position\nwith AHRS fields]
    M & N --> O[emitPosition]
Loading

Priority & Fallback Strategy

When multiple sources are active, LocationManager selects the best available fix using a configurable priority order combined with real-time quality signals. The strategy is conservative — it avoids switching sources unless there is a clear reason to do so, using hysteresis to prevent rapid oscillation.

%%{init: {'theme': 'base', 'themeVariables': {'primaryTextColor': '#000000', 'secondaryTextColor': '#000000', 'tertiaryTextColor': '#000000', 'noteTextColor': '#000000', 'primaryColor': '#909090', 'secondaryColor': '#808080', 'tertiaryColor': '#707070', 'lineColor': '#404040', 'actorLineColor': '#404040', 'signalColor': '#404040', 'actorBkg': '#808080', 'actorTextColor': '#000000', 'noteBkgColor': '#909090', 'stateLabelColor': '#000000', 'compositeBackground': '#a0a0a0'}}}%%
flowchart TD
    A[Position received\nfrom any source] --> B{Higher priority\nthan active source?}
    B -->|No| C{Active source\ndegraded or lost?}
    C -->|No| D[Discard]
    C -->|Yes| E[Switch to best\navailable source]
    B -->|Yes| F{Fix quality\nacceptable?}
    F -->|No| D
    F -->|Yes| G{Hysteresis\nperiod elapsed?}
    G -->|No — too soon\nto switch| D
    G -->|Yes| H[Promote new\nsource to active]
    H --> I[Emit Position]
    E --> I
Loading

Tunable parameters:

  • priorityOrder — explicit ranked list of source IDs (e.g. ['gdl90', 'nmea', 'device'])
  • minUpdateIntervalMs — rate-limits position emissions (e.g. 200 ms prevents flooding)
  • hysteresisMs — milliseconds a higher-priority source must stay healthy before promotion (prevents oscillation)
  • minQuality — minimum quality score (0–1) required to consider a source valid
  • offlineBehavior — what to do when all sources go offline: 'event' (default), 'stale' (re-emit last position with stale: true), or 'retry' (auto-restart sources)
  • retryIntervalMs — milliseconds between restart attempts when offlineBehavior: 'retry'

Getting Started

Install the package:

npm install @visionik/anywhere

Use all available sources, take the best fix automatically:

import { LocationManager, DeviceLocationSource, GDL90Source, NMEASource } from '@visionik/anywhere';

const manager = new LocationManager({
  sources: [
    new DeviceLocationSource({ enableHighAccuracy: true }),
    new GDL90Source({ port: 4000 }),
    new NMEASource({ type: 'udp', port: 10110 }),
  ],
  priorityOrder: ['gdl90', 'nmea', 'device'],
  minUpdateIntervalMs: 200,
  offlineBehavior: 'stale',
});

manager.on('position', (pos) => {
  console.log(`[${pos.source}] ${pos.latitude.toFixed(6)}, ${pos.longitude.toFixed(6)}`);
});

manager.on('sourceChange', (from, to) => {
  console.log(`Active source: ${from ?? 'none'}${to ?? 'offline'}`);
});

manager.start();

Browser-only (no hardware receiver):

import { LocationManager, DeviceLocationSource } from '@visionik/anywhere';

const manager = new LocationManager();
manager.addSource(new DeviceLocationSource({ enableHighAccuracy: true }));
manager.on('position', (pos) => { /* ... */ });
manager.start();

Testing or CI (no hardware needed):

import { LocationManager, SimulatorSource } from '@visionik/anywhere';

const route = [
  { latitude: 37.77, longitude: -122.42, timestamp: new Date(), source: 'sim' },
  { latitude: 37.78, longitude: -122.43, timestamp: new Date(), source: 'sim' },
];

const manager = new LocationManager({
  sources: [new SimulatorSource({ route, intervalMs: 1000, loop: true })],
});
manager.on('position', (pos) => console.log(pos));
manager.start();

API Reference Site

Generate the API reference site with:

task docs:api

The generated site is written to docs/api/index.html.

Runnable Example App

Run the console example that demonstrates SimulatorSource with all three offline behaviors:

task example:efb-demo

It uses examples/efb-demo/index.ts and can also be run directly with npx tsx examples/efb-demo/index.ts.


Roadmap

%%{init: {'theme': 'base', 'themeVariables': {'primaryTextColor': '#000000', 'secondaryTextColor': '#000000', 'tertiaryTextColor': '#000000', 'noteTextColor': '#000000', 'primaryColor': '#909090', 'secondaryColor': '#808080', 'tertiaryColor': '#707070', 'lineColor': '#404040', 'actorLineColor': '#404040', 'signalColor': '#404040', 'actorBkg': '#808080', 'actorTextColor': '#000000', 'noteBkgColor': '#909090', 'stateLabelColor': '#000000', 'compositeBackground': '#a0a0a0'}}}%%
gantt
    title Anywhere Release Roadmap
    dateFormat YYYY-MM
    section v0.1 — Foundation (released)
    Core types, LocationSource, TypedEmitter  :done, 2026-04, 1M
    LocationManager (priority, fallback, offline) :done, 2026-04, 1M
    DeviceLocationSource (W3C Geolocation)    :done, 2026-04, 1M
    NMEA 0183 parser (all transports)         :done, 2026-04, 1M
    GDL-90 UDP parser (Heartbeat + Ownship + AHRS) :done, 2026-04, 1M
    SimulatorSource                           :done, 2026-04, 1M
    section v0.2 — Native Platform Support
    Capacitor bridge (iOS / Android)          :      2026-07, 2M
    Electron / Tauri bridge (Desktop)         :      2026-08, 2M
    Kalman filter position fusion             :      2026-09, 2M
    section v0.3 — UI & Ecosystem
    React & Vue hooks                         :      2026-11, 1M
    Example EFB dashboard                     :      2026-12, 2M
    npm publish v1.0.0                        :      2027-01, 1M
Loading

License

MIT


Made for pilots, builders, and developers who just want to know "where" — from anywhere. Feedback, contributions, and Stratux/GDL-90 test reports are welcome!

About

Unified Location Library — normalize GPS/location data from device GPS, NMEA 0183, GDL-90, and more

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors