Skip to main content

Flutter Native - VPS (Visual Positioning System)

This guide covers how to use VPS (Visual Positioning System) with the Wemap SDK for precise indoor positioning. VPS uses the device camera to recognize surroundings — it does not require location permissions.


Overview

VPS (Visual Positioning System) uses the device camera to capture images of the environment, which are matched against mapped indoor spaces. It provides meter-level accuracy indoors (shopping malls, airports, hospitals, etc.).

VPS does not use GPS or location services. It requires:

  • Camera permission
  • Internet connection
  • ARCore (Android) — Google Play Services for AR — see supported devices
  • Mapped indoor environments

Permissions

VPS requires camera permission only. Location permission is not needed for VPS.

Android

Add to android/app/src/main/AndroidManifest.xml:

<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.INTERNET" />

iOS

Add to ios/Runner/Info.plist:

<key>NSCameraUsageDescription</key>
<string>We need access to your camera for visual positioning (VPS) in indoor spaces.</string>

If you use the permission_handler package, add to your Podfile's post_install block:

config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= [
'$(inherited)',
'PERMISSION_CAMERA=1',
]

Enabling VPS

Set locationSource in MapOptions:

MapOptions(
mapID: yourMapId,
token: yourToken,
environment: Environment.PROD,
locationSource: LocationSource.VPS,
)

Starting and stopping VPS

After permissions are granted, you can start and stop VPS programmatically using MapManager, which is provided in the onMapReady callback:

MethodDescription
mapManager.startVPS()Start VPS scanning to retrieve user position. Ensure camera permission is granted before calling.
mapManager.stopVPS()Stop the ongoing VPS scan.
late MapManager _mapManager;

void _onMapReady(MapData mapData, MapManager mapManager) {
_mapManager = mapManager;
}

// Start VPS scan (after camera permission is granted)
void _startVpsScan() async {
await _mapManager.startVPS();
}

// Stop VPS scan
void _stopVpsScan() async {
await _mapManager.stopVPS();
}

VPS state and callbacks

VPSState

The positioning state is reported via onStateChanged:

StateMeaning
VPSState.NOT_POSITIONINGUser is not localized
VPSState.DEGRADED_POSITIONINGLocalized with reduced accuracy
VPSState.ACCURATE_POSITIONINGLocalized with good accuracy

NotPositioningReason

When not localized, onScanReasonChanged provides a reason:

ReasonMeaning
tooFarFromNavigationToo far from the navigation route
conveyingDetectedOn elevator/escalator/moving walkway
noRelocalizationUnable to relocalize
noneNo specific reason

ScanStatus

onBackgroundScanStatusChanged reports background scan activity:

StatusMeaning
ScanStatus.startedBackground scan has started
ScanStatus.stoppedBackground scan has stopped

Callbacks

CallbackSignatureWhen fired
onStateChangedvoid Function(VPSState state)Positioning state changes
onUserLocalizedvoid Function(Coordinate, Attitude, bool backgroundScan)User is localized (backgroundScan bool indicates weither or not it's from background scan)
onStartedVpsProcessvoid Function()VPS scan starts
onStoppingVpsProcessvoid Function()VPS scan is stopping
onScanReasonChangedvoid Function(NotPositioningReason reason)Reason for not being positioned changes
onBackgroundScanStatusChangedvoid Function(ScanStatus scanStatus)Background scan starts or stops
onVPSBadConnectionvoid Function()VPS connection is poor
onVPSNoConnectionvoid Function()No internet during VPS

Background scan

In addition to the scan initiated by the user, the system can sometimes self-initiate a background scan to improve user tracking. This is based on various conditions, such as distance traveled, time elapsed since the last successful scan, or changed environmental conditions.

When the background scan status changes, you receive events via onBackgroundScanStatusChanged. Here are common scenarios:

  • User walked a long distance without a re-scan and VPS state is DEGRADED_POSITIONING. The system starts a background scan to try to restore ACCURATE_POSITIONING, but it cannot (usually because the phone is targeting the floor). Consider showing a hint such as: "Please hold your phone vertically in front of you so the system can recognize your surroundings" until the background scan stops and/or the VPS state changes to ACCURATE_POSITIONING.
  • User has been standing with the phone for a long time and the system started a background scan, but VPS state is still ACCURATE_POSITIONING. The system suspects upcoming degradation in tracking quality and tries to preserve it. No action is required on your side.

Retrieving user location

Once the user is localized (VPS state is ACCURATE_POSITIONING or DEGRADED_POSITIONING), you can access position and attitude via MapManager:

MethodDescription
mapManager.getUserLocation()Get current user position once. Returns Coordinate? with latitude, longitude, level. Returns null if not localized.
mapManager.getUserLocationStream()Stream<Coordinate> — continuous position updates
mapManager.getUserAttitudeStream()Stream<Attitude> — device heading (headingDegrees 0–360) for the blue dot arrow
void _onMapReady(MapData mapData, MapManager mapManager) {
mapManager.getUserLocation().then((coordinate) {
if (coordinate != null) {
print('User at: ${coordinate.latitude}, ${coordinate.longitude}, level: ${coordinate.level}');
}
});
}

// Or listen to continuous updates
mapManager.getUserLocationStream().listen((coordinate) {
// Handle each position update
});

VPS flow

  1. User taps the user location button (with userLocationButtonEnabled: true)
  2. Camera permission is requested if not yet granted
  3. ScanDialogWidget appears — user starts the scan
  4. Camera turns on; VPSInProcessScreen shows (if vpsInProcessScreen: true) — dotted border, status text, cancel button
  5. User moves the phone to capture surroundings
  6. On success: onUserLocalized fires, onStateChanged reports VPSState.ACCURATE_POSITIONING or DEGRADED_POSITIONING
  7. On failure: dialogs such as VPSBadConnectionDialog or VPSNoConnectionDialog may appear (if enabled)

VPS dialogs and screens

Enable these WemapMap options to improve the VPS experience:

OptionWhen shown
vpsNoConnectionDialogNo internet when starting a VPS scan
vpsBadConnectionDialogScan takes longer than expected (poor connection)
vpsTimedOutDialogScan times out
holdUpPhoneDialogPrompt to hold phone up during background scan
vpsInProcessScreenOverlay during scan (dotted border, status, cancel button)
scanSuggestionDialogSuggests scanning when user is not localized
WemapMap(
options: mapOptions,
userLocationButtonEnabled: true,
vpsNoConnectionDialog: true,
vpsBadConnectionDialog: true,
vpsTimedOutDialog: true,
holdUpPhoneDialog: true,
vpsInProcessScreen: true,
scanSuggestionDialog: true,
)

Complete VPS example

WemapMap(
options: MapOptions(
mapID: yourMapId,
token: yourToken,
environment: Environment.PROD,
locationSource: LocationSource.VPS,
),
userLocationButtonEnabled: true,
vpsNoConnectionDialog: true,
vpsBadConnectionDialog: true,
vpsTimedOutDialog: true,
holdUpPhoneDialog: true,
vpsInProcessScreen: true,
scanSuggestionDialog: true,
onStateChanged: (state) {
// Handle positioning state
},
onUserLocalized: (coordinate, attitude, backgroundScan) {
// User localized — e.g. haptic feedback
},
)

Troubleshooting

IssueSolution
Camera not openingCheck CAMERA permission and usage description (iOS)
"ARCore required" on AndroidEnsure ARCore is installed (Google Play Services for AR)
Scan never completesEnsure good lighting, mapped environment, and stable internet
VPS not availableVerify locationSource: LocationSource.VPS and camera permission