Skip to main content
GuidesAPI ReferenceExamples

External Position Source Navigation

|

In this guide you will learn how to simulate navigation along a route defined by an external data source.

Preview of route to navigate

A navigation is started similar to Route Simpulation With Instructions Example. The key difference is in the startSimulation(..) method. Contine reading.

Creating an external data source

To create an external data source an array of coordinates must be provided. These coordinates would normally be provided by the backend but for our example a simple local array of coordinates will be used.

MainActivity.kt
class MainActivity : AppCompatActivity()
{
companion object
{
val positions = arrayOf(
Pair(48.133931, 11.582914),
Pair(48.134015, 11.583203),
Pair(48.134057, 11.583348),
//...
}
//...
}

To start a navigation a destination must also be provided :

MainActivity.kt
class MainActivity : AppCompatActivity()
{
companion object
{

//...
val destination = Pair(48.17192581, 11.80789822)
}
//...
}

In the code below is shown how to create an external data source.

MainActivity.kt
class MainActivity : AppCompatActivity()
{
//...

override fun onCreate(savedInstanceState: Bundle?)
{
//...
SdkSettings.onMapDataReady = { mapReady ->
i if (mapReady)
{
var externalDataSource: ExternalDataSource?

SdkCall.execute {
externalDataSource =
DataSourceFactory.produceExternal(arrayListOf(EDataType.Position))
externalDataSource?.start()

positionListener = PositionListener { position: PositionData ->
if (position.isValid())
{
navigationService.startNavigation(
Landmark("Poing", destination.first, destination.second),
navigationListener,
routingProgressListener
)

PositionService.removeListener(positionListener)
}
}

PositionService.dataSource = externalDataSource
PositionService.addListener(positionListener)

var index = 0
externalDataSource?.let { dataSource ->
timer = fixedRateTimer("timer", false, 0L, 1000) {
SdkCall.execute {
val externalPosition = PositionData.produce(
System.currentTimeMillis(),
positions[index].first,
positions[index].second,
-1.0,
positions.getBearing(index),
positions.getSpeed(index)
)
externalPosition?.let { pos ->
dataSource.pushData(pos)
}

}
index++
if (index == positions.size)
index = 0
}
}
}
}
//...
}

//...
}

Let's break it down and understand what is happening!

MainActivity.kt
                    //...
externalDataSource =
DataSourceFactory.produceExternal(arrayListOf(EDataType.Position))
externalDataSource?.start()

positionListener = PositionListener { position: PositionData ->
if (position.isValid())
{
navigationService.startNavigation(
Landmark("Poing", destination.first, destination.second),
navigationListener,
routingProgressListener
)

PositionService.removeListener(positionListener)
}
}

PositionService.dataSource = externalDataSource
PositionService.addListener(positionListener)
//...

The arrayListOf(EDataType.Position) tells the factory that the data types that are going to be pushed in data source are of type position. After an external data source is produced succesfully it's started and provided to PositionService. The PositionService receives a PositionListener that will start navigation as soon as teh first valid position is received then remove itself.

After this PositioDate will be fed to the source at fixed time intervals. The PositionData must have : acquisitionTimestamp, latitude and longitude. However for a better representation extra data has been provided.

MainActivity.kt
                    //...
var index = 0
externalDataSource?.let { dataSource ->
timer = fixedRateTimer("timer", false, 0L, 1000) {
SdkCall.execute {
val externalPosition = PositionData.produce(
System.currentTimeMillis(),
positions[index].first,
positions[index].second,
-1.0,
positions.getBearing(index),
positions.getSpeed(index)
)
externalPosition?.let { pos ->
dataSource.pushData(pos)
}

}
index++
if (index == positions.size)
index = 0
}
}

To get bearing value:

MainActivity.kt
/**
* Calculates bearing between 2 points Formula β = atan2(X,Y) where X and Y are two quantities
* that can be calculated based on the given latitude and longitude
* @return Bearing value between point at [index] and previous point.
* If there is no previous point returns -1.0
*/
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
}

To get speed value:

MainActivity.kt
/**
* @return speed value equal with distance between point at [index] and previous point.
* If there is no previous point returns -1.0
*/
fun Array<Pair<Double, Double>>.getSpeed(index: Int): Double
{
if ((index > 0) && (index < size))
return this[index - 1].getDistanceOnGeoid(this[index])
return -1.0
}

/**
* Mathematical formula for calculating real distance between 2 coordinates
* @return real distance between 2 geographical points
*/
fun Pair<Double, Double>.getDistanceOnGeoid(to: Pair<Double, Double>): Double
{
val (latitude1, longitude1) = this
val (latitude2, longitude2) = to
// convert degrees to radians
val lat1 = latitude1 * Math.PI / 180.0
val lon1 = longitude1 * Math.PI / 180.0
val lat2 = latitude2 * Math.PI / 180.0
val lon2 = longitude2 * Math.PI / 180.0

// radius of earth in metres
val r = 6378100.0
// P
val rho1 = r * cos(lat1)
val z1 = r * sin(lat1)
val x1 = rho1 * cos(lon1)
val y1 = rho1 * sin(lon1)
// Q
val rho2 = r * cos(lat2)
val z2 = r * sin(lat2)
val x2 = rho2 * cos(lon2)
val y2 = rho2 * sin(lon2)
// dot product
val dot = (x1 * x2 + y1 * y2 + z1 * z2)
val cosTheta = dot / (r * r)
val theta = acos(cosTheta)
// distance in Metres
return (r * theta)
}