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:
| Field | Type | Notes |
|---|---|---|
position | UserPosition | undefined | A Coordinates with time/accuracy/bearing. Exposes lat/lng (and latitude/longitude), alt, level. Absent before the first fix. |
attitude | Attitude | undefined | Device orientation; attitude.headingDegrees is the heading in degrees. Absent until orientation data is available. |
inclination | number | undefined | Inclination in radians. |
time | number | undefined | Timestamp (ms since epoch). |
accuracy | number | undefined | Accuracy 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
});
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 start → stop → dispose 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 withstart().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)andonError(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();
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);