Skip to main content

Custom positioning

Last updated: March 31, 2026 | 3 minutes read

Learn how to push custom positions into an external DataSourceContext and consume them through PositionContext.

Custom positioning is useful when your app needs to replay known trajectories, inject telemetry from an external system, or test navigation behavior without relying on device GPS.

Tip

For simulation and testing flows, custom and playback data sources let you validate behavior consistently across runs.

Create an external data source

Create an external source that accepts DataType.position values:

guard let externalSource = DataSourceContext(externalDataTypes: [NSNumber(value: DataType.position.rawValue)]) else {
return
}

_ = externalSource.start()

This source will accept DataObject samples pushed by your app. If creation fails, the source is nil, so keep the guard check.

Push custom position samples

Create positions with PositionObject.createPosition(...), then push their underlying DataObject.

let nowMs = Int(Date().timeIntervalSince1970 * 1000)
let custom = PositionObject.createPosition(
nowMs,
latitude: 48.85682,
longitude: 2.34375,
altitude: 0,
course: 0,
speed: 0,
speedAccuracy: 1,
horizontalAccuracy: 5,
verticalAccuracy: 5,
courseAccuracy: 5
)

if let dataObject = custom?.getDataObject() {
_ = externalSource.pushData(dataObject)
}

For more stable map-matching results, provide realistic values for:

  • course - movement heading in degrees
  • speed - speed in meters per second
  • horizontalAccuracy / verticalAccuracy - confidence of the sample

Push updates continuously

In real flows, push multiple samples over time instead of a single point:

let timer = DispatchSource.makeTimerSource(queue: .main)
timer.schedule(deadline: .now(), repeating: .milliseconds(200))
timer.setEventHandler {
let nowMs = Int(Date().timeIntervalSince1970 * 1000)

let sample = PositionObject.createPosition(
nowMs,
latitude: 48.85682,
longitude: 2.34375,
altitude: 0,
course: 45,
speed: 8,
speedAccuracy: 1,
horizontalAccuracy: 4,
verticalAccuracy: 6,
courseAccuracy: 5
)

if let data = sample?.getDataObject() {
_ = externalSource.pushData(data)
}
}
timer.resume()

Read custom data through PositionContext

let positionContext = PositionContext(context: externalSource)
positionContext.startUpdatingPositionDelegate(.position)

if let position = positionContext.getPosition(.position) {
print(position.getCoordinates())
}

If you need road-aware values (such as road modifiers or road speed limits), start updates with .improvedPosition instead of .position.

Remove custom data source

Stop updates and clean resources when done:

positionContext.stopUpdatingPositionDelegate()
_ = externalSource.stop()
warning

Always stop external data sources when no longer needed to avoid conflicting updates in other flows.

If you started a timer or any producer loop, stop it before stopping the source.

Playback and simulation alternatives

Use these source initializers when you need repeatable test runs:

let simulationSource = DataSourceContext(route: route)
let logSource = DataSourceContext(filePath: "/path/to/recording.gpx")

guard let simulationSource, let logSource else { return }
  • DataSourceContext(route:) is useful for deterministic route simulation.
  • DataSourceContext(filePath:) is useful for replaying recorded tracks.