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 SDK). However, it can also be used in combination with the WemapMapSDK
. 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
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
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.