This example runs a real turn-by-turn navigation whose position fixes come from an external data source instead of the device GPS. This is the integration point for any position provider that is not the built-in receiver - a connected external GPS unit, or a recorded track replayed for testing. The on-screen experience is the same as the Route Simulation example - a top panel with the next-turn icon, distance and street name, and a bottom panel with the estimated time of arrival, remaining travel time and remaining travel distance - but each position is pushed into the SDK by the app.
Unlike the simulation example, this is a genuine navigation: navigationService.startNavigation is used (not startSimulation), and the positions that drive it are supplied through a PositionService data source. Here a fixed array of coordinates is fed at one-second intervals to stand in for a live feed.
The coordinates to be replayed and the navigation destination are declared in the companion object. In a real integration the positions would arrive from the backend or hardware feed; here a static array near Munich is used so the example is self-contained.
MainActivity retains a single NavigationService and a NavigationListener. The listener receives the stream of navigation events: when navigation starts, when the next instruction changes, when the status changes, when the destination is reached, on errors, and when a voice instruction should be played. The navRoute helper returns the active route, which the UI uses to read the estimated time of arrival, remaining time and distance.
Once the worldwide road map is downloaded and up to date, startNavigation is called. It builds the external data source, wires it to PositionService, starts navigation on the first valid fix, and then begins pushing positions.
Producing the data source.DataSourceFactory.produceExternal(arrayListOf(EDataType.Position)) declares that this source will emit data of type Position. After start() succeeds it is handed to PositionService via PositionService.dataSource, so the SDK reads positions from it rather than from the device GPS.
Starting navigation on the first fix. A PositionListener is registered on PositionService. When the first valid position arrives, it starts navigation toward the destination landmark and then removes itself, so navigation is started exactly once.
Feeding positions. A fixed-rate timer pushes a new PositionData into the data source every second, looping back to the start of the array when it reaches the end. PositionData.produce requires at least an acquisition timestamp, latitude and longitude; the bearing and speed are supplied as well so the map cursor rotates and animates realistically.
A live feed usually carries bearing and speed, but the static array only has coordinates, so they are computed from consecutive points. getBearing applies the standard initial-bearing formula between the previous and current point; the first point has no predecessor and returns -1.0.
* Bearing formula: β = atan2(X, Y) where X and Y are quantities derived from the two coordinates.
* @return bearing in degrees between the point at [index] and the previous point,
* or -1.0 if there is no previous point.
*/
fun Array<Pair<Double, Double>>.getBearing(index: Int): Double {
if((index >0)&&(index < size)){
val x =cos(this[index].first)*sin(this[index].second -this[index -1].second)
val y =cos(this[index -1].first)*sin(this[index].first)-
sin(this[index -1].first)*cos(this[index].first)*
cos(this[index].second -this[index -1].second)
return(atan2(x, y)*180)/ Math.PI
}
return-1.0
}
getSpeed approximates speed as the great-circle distance between the previous and current point (covered in one second), using getDistanceOnGeoid to compute the metric distance between two coordinates.
The position-feed timer holds native SDK resources, so it must be stopped when the activity is destroyed. onDestroy cancels the timer, clears the SDK listeners and releases the SDK.