Skip to main content

Positioning Guide

The positioning package provides location tracking capabilities using various location sources. This section covers everything you need to know to get started with positioning.

How to Initialize Core SDK with Positioning

The Core SDK must be initialized before creating location sources, as location sources may depend on configuration data from the Core SDK (especially for VPS).

import { core } from '@wemap/core';
import { GnssWifiLocationSource } from '@wemap/positioning';

// Step 1: Initialize Core SDK first
await core.init({
emmid: 'your-map-id',
token: 'your-token',
});

// Step 2: Now you can create location sources
const locationSource = new GnssWifiLocationSource({
// Configuration options
});

Location Sources

A Location Source is a system used to track user position. Wemap provides two main location source types:

GnssWifiLocationSource

Geolocation based on GPS and WiFi positioning. This is the standard location source that works in most environments.

import { GnssWifiLocationSource } from '@wemap/positioning';

const gnssLocationSource = new GnssWifiLocationSource({
// Configuration options
});

// Start the location source
await gnssLocationSource.start();

// Listen for position updates
gnssLocationSource.onUpdate((pose) => {
// Both fields are optional — guard before reading them.
if (pose.position) {
console.log('Position:', pose.position.lat, pose.position.lng);
}
if (pose.attitude) {
console.log('Orientation:', pose.attitude.headingDegrees);
}
});

Check out the API reference for GnssWifiLocationSource →

The Pose object

Every onUpdate callback receives a Pose:

FieldTypeNotes
positionUserPosition | undefinedA Coordinates with time/accuracy/bearing. Exposes lat/lng (and latitude/longitude), alt, level. Absent before the first fix.
attitudeAttitude | undefinedDevice orientation; attitude.headingDegrees is the heading in degrees. Absent until orientation data is available.
inclinationnumber | undefinedInclination in radians.
timenumber | undefinedTimestamp (ms since epoch).
accuracynumber | undefinedAccuracy in meters.

position and attitude are optional — always check they exist before reading them.

VPSLocationSource (Visual Positioning System)

Geolocation based on visual positioning system. VPS uses camera input to provide highly accurate indoor positioning, especially useful in environments where GPS signals are weak or unavailable.

import { VPSLocationSource } from '@wemap/positioning';

const vpsLocationSource = new VPSLocationSource({
// Configuration options
});
info

Wemap VPS must be configured by the Wemap team. Please contact us if you are interested in using it.

For detailed information about VPS, including setup, camera configuration, and scan procedures, see the VPS Location Source guide.

How to Handle Authorization

Location sources require various browser permissions. The positioning package provides helper functions to request these permissions:

Camera Permissions (for VPS)

import { requestCameraPermissions } from '@wemap/camera';

const hasPermission = await requestCameraPermissions();
if (!hasPermission) {
throw new Error('Camera permission denied');
}

Location Permissions (for GNSS/WiFi)

import { requestLocationPermissions } from '@wemap/positioning';

const hasPermission = await requestLocationPermissions();
if (!hasPermission) {
throw new Error('Location permission denied');
}

Device Orientation Permissions (iOS)

On iOS, you need to request device orientation permission. This request needs to be triggered after a user interaction (click, tap, etc.).

import { requestSensorPermissions } from '@wemap/positioning';

const hasPermission = await requestSensorPermissions();
if (!hasPermission) {
throw new Error('Sensor permission denied');
}

Location source lifecycle

Location sources follow a startstopdispose lifecycle, and let you subscribe / unsubscribe to updates and errors:

const source = new GnssWifiLocationSource();

const onPose = (pose) => console.log(pose.position);
source.onUpdate(onPose); // subscribe
await source.start(); // begin emitting updates

// ... later ...
source.offUpdate(onPose); // unsubscribe a specific callback
await source.stop(); // stop emitting; the source can be started again

// When you are done with the source for good, release its resources:
await source.dispose(); // stops it and clears every registered callback
  • start() / stop() — start and stop emitting. A stopped source can be restarted with start().
  • dispose() — permanent teardown: stops the source and removes all update/error/state listeners. Call it when the source is no longer needed (e.g. on view unmount); the instance should be discarded afterwards.
  • onUpdate(cb) / offUpdate(cb) and onError(cb) / offError(cb) — add and remove callbacks. Keep a reference to the callback to remove it later.

Error handling

import { core } from '@wemap/core';
import { GnssWifiLocationSource } from '@wemap/positioning';

try {
// Rejects with an Error if emmid/token are missing, or an HttpError
// (carrying .status) if the livemap can't be fetched (bad token → 401/403,
// unknown emmid → 404).
await core.init({ emmid: 'your-map-id', token: 'your-token' });
} catch (error) {
console.error('SDK init failed:', error);
}

const source = new GnssWifiLocationSource();

// Runtime errors (permission revoked, sensor/provider failures) are delivered
// to the onError callback rather than thrown from start().
source.onError((error) => {
console.error('Location error:', error.message);
});

await source.start();

For VPS, per-scan failures are reported through onError as well — startScan() resolves to false on a scan error rather than rejecting (it only rejects when VPS is unavailable, e.g. no camera). Route calculation (router.directions()) rejects with an HttpError if the request fails.

How to handle Itinerary + Map Matching

Map matching projects your position onto a predefined route, providing more accurate navigation.

Step 1: Calculate a Route

import { Router } from '@wemap/routing';

const router = new Router();

// Calculate route from current position to destination
const itineraries = await router.directions(
{ lat: 48.8566, lng: 2.3522 }, // Origin
{ lat: 48.8606, lng: 2.3376 }, // Destination
'WALK', // Travel mode
);

// Use the first (usually best) itinerary
const itinerary = itineraries[0];

Step 2: Set Itinerary for Map Matching

import { MapMatching } from '@wemap/positioning';

// Set the itinerary for map matching
MapMatching.setItinerary(itinerary);

// Now all location sources will project positions onto this route

Step 3: Receive Map-Matched Positions

locationSource.onUpdate((pose) => {
// Position is automatically projected onto the route
if (pose.position) {
console.log('Map-matched position:', pose.position);
// Use this position for navigation display
}
});

Step 4: Clear Itinerary

MapMatching.clearItinerary();

Complete Map Matching Example

import { core } from '@wemap/core';
import { GnssWifiLocationSource, MapMatching } from '@wemap/positioning';
import { Router } from '@wemap/routing';

async function setupNavigationWithMapMatching() {
// 1. Initialize Core SDK
await core.init({ emmid: '...', token: '...' });

// 2. Create location source
const locationSource = new GnssWifiLocationSource();

// 3. Set up position updates
locationSource.onUpdate((pose) => {
console.log('Position update:', pose.position);
// Update your UI/map with the position
});

// 4. Start location source
await locationSource.start();

// 5. Calculate route
const router = new Router();
const itineraries = await router.directions(
origin,
destination,
'WALK'
);

// 6. Set itinerary for map matching
MapMatching.setItinerary(itineraries[0]);

// Now positions will be automatically projected onto the route
}

// To clear map matching later:
MapMatching.clearItinerary();
info

By default, map matching does not strictly project the position onto the route: a position is snapped to the route only when it lies within maxDistance meters of it (default 30) and within the bearing tolerance. Positions further away are left unprojected. Set useStrict: true to project every position onto the route regardless of distance.

These map-matching parameters are passed in the location source configuration:

const locationSource = new GnssWifiLocationSource({
maxDistance: 20, // snap only within 20m of the route (default 30)
useStrict: false, // set true to always project onto the route
});

Integrate Map Matching with your own itinerary routing system

Our map matching system is not stricly tied to the routing package. You can use it with your own itinerary routing system.

import { Itinerary, ItineraryInfoManager } from '@wemap/routing';
import { GnssWifiLocationSource, MapMatching, type Pose, Coordinates, UserPosition } from '@wemap/positioning';

const locationSource = new GnssWifiLocationSource();
await locationSource.start();

let userPosition: UserPosition | null = null;

locationSource.onUpdate((pose: Pose) => {
// pose.position is a UserPosition (a Coordinates) or undefined before the first fix
userPosition = pose.position ?? null;
});

/*
* Use your own itinerary routing system to get the itinerary
*/
const itinerary = await yourItineraryRoutingSystem.getItineraries(userPosition, destination);
const orderedCoordinates = itinerary.coords.map((point) => new Coordinates(point.lat, point.lng));

/**
* Transform your itinerary shape to the one expected by the map matching system
* You can also use the Itinerary.fromOrderedPointsArray method
*/
const transformedItinerary = Itinerary.fromOrderedCoordinates(orderedCoordinates, itinerary.origin, itinerary.destination);

MapMatching.setItinerary(transformedItinerary);