PositioningSDK - Getting started
Installation
Before you start developing your application with WemapSDKs, you need to configure your credentials and add the SDK as a dependency.
The code examples below demonstrate how to configure the WemapPositioningSDK
to work as a standalone solution outside of the Wemap Map & AR ecosystem (i.e., with another Map/AR SDK). However, it can also be used in combination with the WemapMapSDK
or WemapGeoARSDK
. Instructions are available here.
Adding the Dependency
To add WemapSDKs to your app:
Add Dependencies in the
Podfile
Add the required WemapSDK pods to your app:
use_frameworks!
target 'TargetNameOfYourApp' do
# Add the dependency for the WemapPositioningSDK library
pod 'WemapPositioningSDK/VPSARKit', '<version>' # Wemap VPS ARKit Location Source
pod 'WemapPositioningSDK/GPS', '<version>' # GPS Location Source
pod 'WemapPositioningSDKPolestar', '<version>' # Polestar Location Source
endEnsure Minimum iOS Version
Your project must target iOS 12.0 or later:
platform :ios, '12.0'
Install Dependencies and Open the Project
Run the following command to install the pods:
AWS_ACCESS_KEY_ID=*** \
AWS_SECRET_ACCESS_KEY=*** \
AWS_REGION=*** \
bundle exec pod install --repo-updateThen, open your project in Xcode:
open your-project.xcworkspace
Location Sources
Wemap provides various location sources to track the user's location on the map.
To use any Location Source, you must have a mapID
and token
. For more details, please contact the Wemap team.
To set a specific location source, follow these steps:
- Add the relevant
WemapPositioningSDK
library to your project dependencies. - Fetch
MapData
using yourmapID
andtoken
. - Create a
LocationSource
usingMapData
. - Assign location source delegates to listen for location updates.
Wemap VPS ARKit Location Source
Fetching MapData
ServiceFactory
.getMapService()
.map(byID: 19158, token: "GUHTU6TYAWWQHUSR5Z5JZNMXX")
.observe(on: MainScheduler.asyncInstance)
.subscribe(onSuccess: { mapData in
setupLocationSource(mapData: mapData)
}, onFailure: {
debugPrint("Failed to get map data with error - \($0)")
})
.disposed(by: disposeBag)
Setting Up the VPS ARKit Location Source
Once you have the MapData
, create a VPSARKitLocationSource
instance, assign delegates, and start it:
func setupLocationSource(mapData: MapData) {
vpsLocationSource = VPSARKitLocationSource(mapData: mapData)
vpsLocationSource.delegate = self
vpsLocationSource.vpsDelegate = self
vpsLocationSource.start()
}
Handling Location Source State Changes
You must handle state changes in VPSARKitLocationSourceDelegate
, as users may need to scan their environment.
extension VPSViewController: VPSARKitLocationSourceDelegate {
func locationSource(_ locationSource: VPSARKitLocationSource, didChangeState state: VPSARKitLocationSource.State) {
// Handle state changes
}
func locationSource(_ locationSource: VPSARKitLocationSource, didChangeScanStatus status: VPSARKitLocationSource.ScanStatus) {
// Handle scan status changes
}
}
Retrieving User's Coordinate and Attitude
To get the user’s coordinate and attitude, implement LocationSourceDelegate
:
extension VPSViewController: LocationSourceDelegate {
func locationSource(_ locationSource: any LocationSource, didUpdateCoordinate coordinate: Coordinate) {
// Pass the updated coordinate to your map implementation
}
func locationSource(_ locationSource: any LocationSource, didUpdateAttitude attitude: Attitude) {
// Pass the updated attitude to your map implementation
}
func locationSource(_ locationSource: any LocationSource, didFailWithError error: any Error) {
// Handle errors
}
}
Scanning the Environment
To start tracking the user's location, allow them to scan their environment:
func startScan() {
vpsLocationSource.startScan()
}
Once the system successfully recognizes the user's location, it will report the accuratePositioning
state to VPSARKitLocationSourceDelegate
. Shortly afterward, you will start receiving updated Coordinate
and Attitude
values in LocationSourceDelegate
.
The VPS system will also report:
notPositioning
– indicates that the system hasn't yet recognized the environment and a VPS scan is required. At this point, you should present the camera view to the user to allow them to scan usingvpsLocationSource.startScan()
.degradedPositioning
– indicates that user location tracking is limited for various reasons (see reasons in the API reference documentation). A scan is recommended, but not mandatory, to restore theaccuratePositioning
state. We suggest a subtle UI indication when this state occurs, such as a location icon warning or a small toast message prompting the user to rescan.
Assigning an Itinerary
Once you start receiving updated Coordinate
s, you can assign an itinerary to the VPSARKitLocationSource
. Assigning an itinerary to VPSARKitLocationSource
when a user is following an A→B itinerary enhances the overall navigation experience (e.g., itinerary projections, conveyor detection, etc.).
For example, when a conveyor is detected, the system will prompt you to rescan the environment to restore tracking.
Below are two examples demonstrating how to obtain an itinerary and assign it to VPSARKitLocationSource
.
Itinerary from Wemap API
private func calculateItinerary() {
let origin = Coordinate(latitude: 48.88007462, longitude: 2.35591097, level: 0)
let destination = Coordinate(latitude: 48.88141308, longitude: 2.35747255, level: -2)
ServiceFactory
.getItineraryProvider()
.itineraries(origin: origin, destination: destination, mapId: mapData!.id)
.subscribe(onSuccess: { itineraries in
self.vpsLocationSource.itinerary = itineraries.first
}, onFailure: { error in
debugPrint("Failed to calculate itineraries with error: \(error)")
})
.disposed(by: disposeBag)
}
Manually created Itinerary
You can create an itinerary by providing the minimum necessary information, as shown below.
An Itinerary consists of an origin
, a destination
, and a list of segments
, where:
origin
is the starting point of the itinerary.destination
is the ending point of the itinerary.segments
is a collection of segment objects, where each:segment
is a struct with two connected points (p1
,p2
) along the itinerary, and optionally alevelChange
struct that describes the type of level transition.
private func hardcodedItinerary() {
let origin = Coordinate(latitude: 48.88007462, longitude: 2.35591097, level: 0)
let destination = Coordinate(latitude: 48.88141308, longitude: 2.35747255, level: -2)
let coordinatesLevel0 = [
[ 2.3559003, 48.88005135 ],
...
[ 2.35657153, 48.88013655 ]
].map { Coordinate(latitude: $0[1], longitude: $0[0], level: 0) }
let legSegmentsLevel0 = LegSegment.fromCoordinates(coordinatesLevel0)
let coordinatesFrom0ToMinus1 = [
[ 2.35657153, 48.88013655 ],
[ 2.3567008, 48.8801748 ]
].map { Coordinate(latitude: $0[1], longitude: $0[0], levels: [-1, 0]) }
let levelChangeFrom0ToMinus1 = LevelChange(difference: -1, direction: .down, type: .escalator)
let legSegmentsFrom0ToMinus1 = LegSegment.fromCoordinates(coordinatesFrom0ToMinus1, levelChange: levelChangeFrom0ToMinus1)
let coordinatesLevelMinus1 = [
[ 2.3567008, 48.8801748 ],
...
[ 2.357253, 48.88061996 ]
].map { Coordinate(latitude: $0[1], longitude: $0[0], level: -1) }
let legSegmentsLevelMinus1 = LegSegment.fromCoordinates(coordinatesLevelMinus1)
let coordinatesFromMinus1ToMinus2 = [
[ 2.357253, 48.88061996 ],
[ 2.35727559, 48.88066565 ]
].map { Coordinate(latitude: $0[1], longitude: $0[0], levels: [-2, -1]) }
let levelChangeFromMinus1ToMinus2 = LevelChange(difference: -1, direction: .down, type: .escalator)
let legSegmentsFromMinus1ToMinus2 = LegSegment.fromCoordinates(coordinatesFromMinus1ToMinus2, levelChange: levelChangeFromMinus1ToMinus2)
let coordinatesLevelMinus2 = [
[ 2.35727559, 48.88066565 ],
...
[ 2.35748253, 48.88143515 ]
].map { Coordinate(latitude: $0[1], longitude: $0[0], level: -2) }
let legSegmentsLevelMinus2 = LegSegment.fromCoordinates(coordinatesLevelMinus2)
let segments = legSegmentsLevel0 + legSegmentsFrom0ToMinus1 + legSegmentsLevelMinus1 + legSegmentsFromMinus1ToMinus2 + legSegmentsLevelMinus2
vpsLocationSource.itinerary = .init(origin: origin, destination: destination, segments: segments)
}
You can also check out an example of how a sample GeoJSON is transformed into a Wemap Itinerary in our GitHub repository.
Important note
Starting from iOS 18, there is a bug where ARSession
is automatically stopped by ARView
when the view is dismissed or hidden.
As a result, the positioning process stops after a few seconds.
If you are using ARView
, we recommend a workaround in our sample application
GPS Location Source
Once you have the MapData
, create a GPSLocationSource
instance.
Setting Up the GPS Location Source
func setupLocationSource(mapData: MapData) {
let source = GPSLocationSource(mapData: mapData)
source.start()
}
Polestar Location Source
Once you have the MapData
, create a PolestarLocationSource
instance.
Setting Up the Polestar Location Source
func setupLocationSource(mapData: MapData) {
let source = PolestarLocationSource(mapData: mapData, apiKey: "emulator") // change 'emulator' to your Polestar API key
source.start()
}
Examples
For additional examples and sample implementations of WemapSDKs, visit the official GitHub repository.
Clone the repository and follow the README instructions to run the sample application.