Navigation¶
This section explains the implementation of real-time turn-by-turn navigational instructions, both as visual and voice instructions.
Important
Navigation features use location to function properly. You can read all about location on Location page.
Navigation Warnings¶
The Sygic Maps SDK for Android supports navigation on pedestrian and car routes. Using this feature, your app can check the current device position against a calculated route and provide just-in-time navigational instructions, both as visual and voice instructions. Turn-by-Turn Navigation Mode, which takes the calculated route and matches the current position against the route is supported for walking and driving.
While navigating, application can register a listener for warning notifications and display all of these
notifications. All the types of notifications are listed in NavigationManager
class.
The following table shows the name of the available listeners and the information provided by them.
Listener name | Function |
---|---|
OnBetterRouteListener | Whether a faster route has been found |
OnDirectionListener | Whether the direction has changed |
OnSpeedLimitListener | Whether the user has exceeded the speed limit |
OnSignpostListener | Whether the signpost has updated |
OnLaneListener | Whether the lane information has changed |
OnPlaceListener | Whether the nearby place info has changed |
OnIncidentListener | Whether an incident is ahead |
OnRailwayCrossingListener | Whether a railway crossing is ahead |
OnHighwayExitListener | Whether a highway exit is ahead |
OnSharpCurveListener | Whether a sharp curve is ahead |
OnWaypointPassListener | Whether the vehicle passed by a waypoint |
OnTrafficChangedListener | Whether the traffic data has updated |
OnRouteChangedListener | Whether the route has changed |
OnRecomputeProgressListener | Whether the route recompute progress has changed (100 is the max value) |
OnBatteryCapacityListener | Whether the battery capacity has changed |
WaypointOutOfRangeListener | Whether any waypoint becomes out of reach |
TruckAidListener | Whether there's a truck restriction ahead |
Registering a listener has the following pattern:
// create listener
val onSpeedLimitListener = NavigationManager.OnSpeedLimitListener { speedLimitInfo ->
// info about changed value
Log.d("TAG", "SpeedLimit: ${speedLimitInfo.speedLimit}")
Log.d("TAG", "NextSpeedLimit: ${speedLimitInfo.nextSpeedLimit}")
// ...
}
// register the listener
NavigationManagerProvider.getInstance(object : CoreInitCallback<NavigationManager> {
override fun onInstance(navigationManager: NavigationManager) {
navigationManager.addOnSpeedLimitListener(onSpeedLimitListener)
}
override fun onError(error: CoreInitException) {}
})
Don't forget to unregister the listener when you don't need it.
navigationManager.removeOnSpeedLimitListener(onSpeedLimitListener)
Map Matching During Navigation¶
Map Matching is automatically enabled in both navigation mode and tracking mode. In simulation mode, the map-matched position is simulated along the route with a user-defined speed.
Handling route changes when you steer away from the route¶
When you leave the route that you have chosen for navigation, the SDK handles the recompute internally
and sets the new route for navigation as well. All you need to do is to remove the old MapRoute
and
add a new one, using the route that you get in the onRouteChanged
listener callback.
Voice Instructions¶
Voice instructions are available in the Sygic Maps SDK as voice packages. Voice packages are available in two forms: pre-packaged or downloadable through the voice catalog. You can set a voice package to be used for navigational instructions. More about audio options can be found here
The Sygic Maps SDK supports two types of voice packages: text-to-speech or pre-recorded. Pre-recorded voice skins provide basic maneuver instructions, such as "turn right in 100 meters" instruction, while text-to-speech voices also support spoken street names, such as "turn right in 100 meters onto Wall Street" instruction.
At first, you need to get the VoiceManager
and VoiceDownload
and register them:
var voiceDownload: VoiceDownload
VoiceDownloadProvider.getInstance(object : CoreInitCallback<VoiceDownload> {
override fun onInstance(instance: VoiceDownload) {
voiceDownload = instance
}
override fun onError(error: CoreInitException) {}
})
var voiceManager: VoiceManager
VoiceManagerProvider.getInstance(object : CoreInitCallback<VoiceManager> {
override fun onInstance(instance: VoiceManager) {
voiceManager = instance
}
override fun onError(error: CoreInitException) {}
})
The following code shows how to get the list of installed voices and how to set the voice:
voiceManager.getInstalledVoices { voiceList, result ->
voiceList.firstOrNull()?.let { voice ->
voiceManager.voice = voice
}
}
To get all the available voices, you call VoiceDownload.getAvailableVoices():
voiceDownload.getAvailableVoiceList { voiceList, result ->
voiceList.forEach { voiceEntry ->
val name = voiceEntry.name
val language = voiceEntry.language
val tts = voiceEntry.isTts
when (voiceEntry.status) {
VoiceEntry.VoicePackageStatus.NotInstalled -> {
// your code
}
VoiceEntry.VoicePackageStatus.Installed -> {
// your code
}
else -> {
// your code
}
}
}
}
Then you can download voices.
voiceDownload.addVoiceInstallationListener(object : VoiceInstallListener {
override fun onVoiceInstallProgress(entry: VoiceEntry?, bytesDownloaded: Long, totalBytes: Long) {
// your code
}
override fun onVoiceInstallFinished(entry: VoiceEntry?, result: OperationStatus) {
// your code
}
override fun onVoiceUninstallFinished(entry: VoiceEntry?, result: OperationStatus) {
// your code
}
})
voiceDownload.installVoice(voiceEntry) // voiceEntry is an object returned from getAvailableVoiceList
You can register an AudioInstructionListener in which you can either tell our SDK to play the instruction (returning false from the method, like below) or play any information from the direction info however you want (in that case, you need to return true in the callback).
navigationManager.setAudioInstructionListener { directionInfo ->
val distance = directionInfo.distance
// ...
true
}
This is a list of the potential TTS languages that are supported. Actual audio playback depends on the supported languages in the user's installed version of Android.
- English (US)
- English (UK)
- French (France)
- French (Canada)
- German
- Spanish (Spain)
- Spanish (Mexico)
- Indonesian
- Italian
- Norwegian
- Portuguese (Portugal)
- Portuguese (Brazil)
- Russian
- Swedish
- Finnish
- Danish
- Korean
- Chinese (Taiwanese Mandarin)
- Turkish
- Czech
- Polish
- Ukrainian
Traffic¶
Traffic information show traffic incidents on the map. Traffic visualization requires a network data connection to download real time traffic information. However, traffic information may continue to be displayed thereafter without a connection until the traffic events expire or the visibility is toggled.
Info
Please note that if your license does not contain traffic, you will not receive any traffic information.
You can turn traffic information on and off like this:
mTrafficManager = TrafficManagerProvider.getInstance().get()
mTrafficManager.enableTrafficService()
mTrafficManager.disableTrafficService()
There are different types of traffic information and how it's displayed on map. The most common ones are:
- closure (always grey),
- traffic jam (red, orange, green depending on how big the delay is),
- roadworks (green, yellow, red - depending on the delay),
- some local events like icy roads or strong wind.
Signposts¶
Signs represent textual and graphic information which are along the route. The information is always represented as text or pictogram. Signpost information may be used for route guidance and map display. A navigation system may prefer using the signpost text rather than the street/ramp name as the latter may not always match what is on the sign in reality and may confuse a user. The signpost feature supports the user navigating through complex situations and provides a conformation for a maneuver by presenting the same direction information as shown on the street signs in reality.
Based on the map attributes, the Sygic Maps SDK aggregates the following information into a Signpost object:
- Route number
- Exit number
- Exit name
- Pictogram
- Place name
- Background color
- Text color
Following example shows, how to get these information from the data structure, which the Android SDK offers.
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import com.sygic.sdk.context.CoreInitCallback
import com.sygic.sdk.context.CoreInitException
import com.sygic.sdk.navigation.NavigationManager
import com.sygic.sdk.navigation.NavigationManagerProvider
import com.sygic.sdk.navigation.routeeventnotifications.SignpostInfo
class SignpostFragment : Fragment(), NavigationManager.OnSignpostListener {
private var navigationManager: NavigationManager? = null
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_signpost, container, false)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
NavigationManagerProvider.getInstance(object : CoreInitCallback<NavigationManager> {
override fun onInstance(instance: NavigationManager) {
navigationManager = instance
navigationManager?.addOnSignpostListener(this@SignpostFragment)
}
override fun onError(error: CoreInitException) {}
})
}
override fun onDestroy() {
super.onDestroy()
navigationManager?.removeOnSignpostListener(this)
}
override fun onSignpostChanged(info: MutableList<SignpostInfo>) {
if (info.isEmpty()) {
return
}
with(info.first()) {
val textColor = textColor
val backgroundColor = backgroundColor
val distanceToSignpost = distance
val titles: MutableList<String> = mutableListOf()
val roadNumbers: MutableList<String> = mutableListOf()
val exits: MutableList<String> = mutableListOf()
val exitNumbers: MutableList<String> = mutableListOf()
signElements.forEach { signpostElement ->
when (signpostElement.elementType) {
SignpostInfo.SignElement.SignElementType.PlaceName,
SignpostInfo.SignElement.SignElementType.StreetName,
SignpostInfo.SignElement.SignElementType.OtherDestination -> {
titles.add(signpostElement.text)
}
SignpostInfo.SignElement.SignElementType.RouteNumber -> {
roadNumbers.add(signpostElement.text)
}
SignpostInfo.SignElement.SignElementType.ExitName -> {
exits.add(signpostElement.text)
}
SignpostInfo.SignElement.SignElementType.ExitNumber -> {
exitNumbers.add(signpostElement.text)
}
else -> {
// other code
}
}
}
// do something with the collected data
}
}
}
Directions¶
To provide more detailed information in signpost, you can display the direction which the user should follow.
The Directions feature allows developers to define and display routes between a start and a destination point
within their application. NavigationManager.OnDirectionListener
can return the following information:
- Direction distance
- Next direction distance
- Maneuver type - see the all available ManeuverTypes
- Current and next road names
- Current and next road numbers
- Whether you are in a tunnel and the remaining distance to its end
The following example shows how to get these information from the data structure, which the Android SDK offers.
import android.os.Bundle
import android.util.Log
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.sygic.sdk.context.CoreInitCallback
import com.sygic.sdk.context.CoreInitException
import com.sygic.sdk.navigation.NavigationManager
import com.sygic.sdk.navigation.NavigationManagerProvider
import com.sygic.sdk.navigation.routeeventnotifications.DirectionInfo
class DirectionFragment : Fragment(), NavigationManager.OnDirectionListener {
private var navigationManager: NavigationManager? = null
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_direction, container, false)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
NavigationManagerProvider.getInstance(object : CoreInitCallback<NavigationManager> {
override fun onInstance(instance: NavigationManager) {
navigationManager = instance
navigationManager?.addOnDirectionListener(this@DirectionFragment)
}
override fun onError(error: CoreInitException) {}
})
}
override fun onDirectionInfoChanged(directionInfo: DirectionInfo) {
val distance = directionInfo.distance
val nextDistance = directionInfo.nextDistance
with(directionInfo.primary) {
Log.d("TAG", "Direction of type $type on the road $roadName. Next road is $nextRoadName")
}
}
}
With these data now you just have to draw them into your view and create your own signposts. For example like this:
Junction Info¶
Junction info is one of our advanced safety features. When you are approaching a complex highway intersection,
a SingpostJunctionInfo
object will be set in a OnSignpostListener
. It contains info about lanes both
continuing on highway and on the exit. You can then display this information, e.g. as full screen diagram.
val naviSignListener = NavigationManager.OnSignpostListener { listOfSignposts ->
listOfSignposts.forEach { signpostInfo ->
val junctionInfo = signpostInfo.junctionInfo
if (junctionInfo.areaType != SignpostJunctionInfo.AreaType.Unknown) {
@SignpostJunctionInfo.TurnDirection val turnDirection =
junctionInfo.turnDirection
@SignpostJunctionInfo.TurnType val turnType = junctionInfo.turnType
val fromLanes = junctionInfo.fromLanesCount
val leftLanes = junctionInfo.toLeftLanesCount
val rightLanes = junctionInfo.toRightLanesCount
// display junction info
return@OnSignpostListener
}
}
// no junction info
}
When you have SignpostJunctionInfo
with getFromLanesCount() == 5
, getToLeftLanesCount() == 4
,
getToRightLanesCount() == 2
, it can be displayed as shown below.
Speed Limit¶
Speed limits indicate the legal speed for a vehicle. You can get the value either in mph or in km/h.
You can register OnSpeedLimitListener
to receive events about speed limit change. The class containing
information about speed limit is SpeedLimitInfo
.
val speedLimitListener = NavigationManager.OnSpeedLimitListener { speedLimitInfo ->
val speedLimit = speedLimitInfo.speedLimit
val currentSpeed = speedLimitInfo.currentSpeed
if (speedLimit < currentSpeed) {
// vehicle is speeding
}
val nextSpeedLimit = speedLimitInfo.nextSpeedLimit
val distance = speedLimitInfo.nextSpeedLimitDistance
if (speedLimit != nextSpeedLimit) {
// eg. show warning about limit change
}
@SpeedLimitInfo.SpeedUnits val countrySpeedUnits = speedLimitInfo.countrySpeedUnits
val naturalLimit = speedLimitInfo.getCurrentSpeedLimit(countrySpeedUnits)
// natural limit is in country's natural units, so rounding and decimals can be avoided
}
Route Simulation¶
You can quickly start a route simulation by requesting an instance of RouteDemonstrateSimulator
and passing
it the computed route:
Do not forget to call .stop()
upon cancelling the route or if you no longer need the simulation to prevent
unwanted behavior.
Also please note that you need to call the setRouteForNavigation
method of NavigationManager
class before starting
the simulator.
RouteDemonstrateSimulatorProvider.getInstance(route, object : CoreInitCallback<RouteDemonstrateSimulator> {
override fun onInstance(routeDemonstrateSimulator: RouteDemonstrateSimulator) {
routeDemonstrateSimulator.start()
}
override fun onError(error: CoreInitException) {}
})
Scout Compute¶
What is the Scout compute and how does it work?
Let's imagine the user is navigating home from work and new traffic jams are reported along his route. The user
would like to know this and be offered a new, faster route.
This is what the scout compute does - it's how we call it, but you will actually use the OnBetterRouteListener
to achieve the results. The scout compute runs every 10 minutes in the offline compute and every 5 minutes if you
chose the online compute. The system will take several variables into account before offering the better
route - for example, a new route won't be offered if the time that the user would save is less than 1 minute etc.
Here's how to work with the OnBetterRouteListener
. In this case, the it
is an object of BetterRouteInfo
.
mNavigationManager.addOnBetterRouteListener {
println("Great! A faster route has been found")
println("Saved time: " + it.timeDiff)
println("Saved distance: " + it.lengthDiff + "m")
println("Point of detour: " + it.splitPoint.toString()) // GeoCoordinates
// you can show it to the user on the map and let him decide
// if the user chooses the new one, you need to set it for navigation
mNavigationManager.setRouteForNavigation(it.alternativeRoute)
// also don't forget to remove the old route from the mapView and add the new one!
}
Adjust the scout compute options¶
Since the version 19.3.0, several adjustments can be made to the way the scout compute is calculated.
You can adjust these to your own liking in the JSON Configuration via the
Navigation
-> ScoutSettings
.
This comprises the online and offline update intervals, the minimal saved time and the coefficient of the saved time.
To explain all of those, let's start with the intervals.
As we have seen above, the default values for offline and online scout triggers are 10 and 5 minutes, respectively.
To adjust the interval of the offline scout compute, change the value of offline_update_interval
(it is in seconds).
To adjust the interval of the online scout compute, change the value of online_update_interval
.
Note
Please note that the minimal value of the online interval is 5 minutes (300 seconds). If you enter a smaller value, it will automatically default to 300 seconds.
To adjust the value of the time difference that's necessary to exist between the two routes for the scout compute to
notify you, change the value of minimal_saved_time
. The value should be in seconds.
The saved_time_coefficient
is yet another value which determines whether the new route will be offered or not.
For the scout route to be offered, its time has to be lower than the actual remaining time, the time difference between
the two routes has to be bigger than the minimal_saved_time
value and the time difference also has to be higher than
the minimum of the two:
saved_time_coefficient
times the actual remaining time- 10 minutes
Info
That means if your actual remaining time is 5 minutes, the time of the new route is 2 minutes, your minimal saved time is set to 60 seconds and the saved time coefficient is set to 0.05, the new route will be offered, because 120s < 300s, the saved time (180s) is higher than 60s and the saved time (180s) is higher than 0.05*300 = 15s.
Incidents¶
Our Speed Camera database consists of more than 110 000 fixed speed cameras, red light cameras and average speed checks all over the world. On top of that, the community reports tens of thousands of additional mobile speed cameras and police checks every day. Incidents also include school zones, crashes and more.
How to handle incidents?¶
You can use two ways to obtain information about incidents on your route:
- the
OnIncidentListener
analyzer that can be found in theNavigationManager
- the
exploreIncidentsOnRoute()
that can be used via theRouteExplorer
You can retrieve basic information about incidents ahead of you by using the OnIncidentListener
:
mNavigationManager.addOnIncidentListener {
it.distance // distance to the incident
it.incidentLink // the incident link
it.recommendedSpeed // the recommended speed
}
The recommended speed does not change for a fixed speed camera. Therefore, while you are approaching a speed camera,
the speed stays at 60km/h for example. However, section radars exist, too. These are the RadarStaticAverageSpeed
,
RadarStaticAverageSpeedMiddle
and RadarStaticAverageSpeedEnd
ones. If you approach one of the latter two, the recommendedSpeed
changes according to the speed of the vehicle - it is calculated internally using the speed and distance.
The incident link contains information about the location, provider, or type.
Simple Lane Assistant¶
It's possible to retrieve simple lane information on some roads. This is necessary if you would like to show
the arrows on your route.
You get the information via the Navigation Manager's OnLaneListener
.
At first you would need to create functions that would convert the instructions into images. There's two of them,
because you may get two Arrows
in a single lane - that's where we use the stackable resource.
@DrawableRes
fun getDirectionDrawableResStackable(@LaneInfo.Lane.Direction direction: Int): Int {
return when (direction) {
LaneInfo.Lane.Direction.Straight -> R.drawable.laneassist_stackable_straight
LaneInfo.Lane.Direction.Left -> R.drawable.laneassist_stackable_left
LaneInfo.Lane.Direction.HalfLeft -> R.drawable.laneassist_stackable_halfleft
LaneInfo.Lane.Direction.SharpLeft -> R.drawable.laneassist_stackable_sharpleft
LaneInfo.Lane.Direction.Right -> R.drawable.laneassist_stackable_right
LaneInfo.Lane.Direction.HalfRight -> R.drawable.laneassist_stackable_halfright
LaneInfo.Lane.Direction.SharpRight -> R.drawable.laneassist_stackable_sharpright
LaneInfo.Lane.Direction.UTurnLeft -> R.drawable.laneassist_stackable_uturnleft
LaneInfo.Lane.Direction.UTurnRight -> R.drawable.laneassist_stackable_uturnright
else -> 0
}
}
@DrawableRes
fun getDirectionDrawableRes(@LaneInfo.Lane.Direction direction: Int): Int {
return when (direction) {
LaneInfo.Lane.Direction.Straight -> R.drawable.laneassist_straight
LaneInfo.Lane.Direction.Left -> R.drawable.laneassist_left_45
LaneInfo.Lane.Direction.HalfLeft -> R.drawable.laneassist_left_90
LaneInfo.Lane.Direction.SharpLeft -> R.drawable.laneassist_left_135
LaneInfo.Lane.Direction.Right -> R.drawable.laneassist_right_45
LaneInfo.Lane.Direction.HalfRight -> R.drawable.laneassist_right_90
LaneInfo.Lane.Direction.SharpRight -> R.drawable.laneassist_right_135
LaneInfo.Lane.Direction.UTurnLeft -> R.drawable.laneassist_left_180
LaneInfo.Lane.Direction.UTurnRight -> R.drawable.laneassist_right_180
else -> 0
}
}
Then let's create a data class Arrow
that will represent one arrow. This way the work with an arrow
will be easier, but you may create your own representation.
data class Arrow(val isSelected: Boolean, @DrawableRes val directionDrawableRes: Int) {}
And next we can work with the listener like this :
// here we get the navigation manager
mNavigationManager = NavigationManagerProvider.getInstance().get()
// register a listener
mNavigationManager.addOnLaneListener { laneInfo ->
val simpleLanesInfo = laneInfo.simpleLanesInfo
val arrows = mutableListOf<Arrow>()
simpleLanesInfo?.let {
val lanes = simpleLanesInfo.lanes
lanes.forEach { lane ->
// if there are more direction arrows in one lane
if (lane.arrows.size > 1) {
lane.arrows.forEach { arrow ->
// we will create an Arrow object and add it to the list
arrows.add(Arrow(arrow.isHighlighted, getDirectionDrawableResStackable(arrow.direction)))
}
} else {
// if there's just one direction arrow in a lane
lane.arrows.forEach {arrow ->
// we will create an Arrow object and add it to the list
arrows.add(Arrow(arrow.isHighlighted, getDirectionDrawableRes(arrow.direction)))
}
}
}
}
// then you would return the arrows to where you will draw them
}
Vehicle aid analyzer¶
Warning
TruckAidInfo
is deprecated, use VehicleAidInfo
and OnVehicleAidListener
instead.
There's a possibility that you would like to show the upcoming restrictions directly in the map so that the driver sees it and is aware of the road. These restrictions contain types such as whether the road is a blind road, if it is prohibited to turn there, or what the maximum weight is.
NavigationManagerProvider.getInstance().get().addOnVehicleAidListener { vehicleAidInfoList ->
for (vehicleAidInfo in vehicleAidInfoList) {
print(vehicleAidInfo.distanceToManeuver)
print(vehicleAidInfo.importance) // standard, minor, major
print(vehicleAidInfo.restriction.type) // max weight, dead end etc.
print(vehicleAidInfo.restriction.value) // value for max weight / height, if any
}
}
Since SDK 21.6.0 it is possible to directly add the Vehicle Aid Info object to the map:
NavigationManagerProvider.getInstance().get().addOnVehicleAidListener { vehicleAidInfoList ->
vehicleAidInfoList.forEach {
mapView.mapDataModel.addMapObject(
MapTrafficSign.fromVehicleAid(it)
.build())
}
}
Tunnel interpolation switch¶
Info
Available in SDK21.2.0
By default, when entering a tunnel, the position of the vehicle is simulated based on the last known speed. However, as some manufacturers may calculate the speed through other means than GPS signal and there may be intersections or bifurcations in tunnels, we have added the option to turn off the simulation of the position.
You can choose between two modes: TunnelPositionMode.RawSignal
and TunnelPositionMode.Interpolation
.
PositionManagerProvider.getInstance().get().setTunnelPositionMode(
TunnelPositionMode.Interpolation, object: SetTunnelPositionModeListener {
override fun onSet() {
}
}
)
You can get the currently used mode using the getter:
PositionManagerProvider.getInstance().get().getTunnelPositionMode(object : GetTunnelPositionModeListener {
override fun onGet(mode: TunnelPositionMode) {
mode.name
}
})
Adding custom Incidents¶
Please note that if you define your own incident category, it will be necessary to define a new icon in the skin file, else it won't be displayed. However, if you choose one of the already existing ones, it will display the icon that is already assigned to that incident type. Here's a brief example of how to add your own speed camera:
val speedCamera = SpeedCamera(
IncidentId("26a69832-7f72-42ba-8f1d-394811376579"),
GeoCoordinates(48.10095535808773, 17.234824479529344),
IncidentType.RadarStaticSpeed,
1713510440, // 19.4.2024
359F,
true,
80
)
val incidentData = IncidentData(
speedCamera,
IncidentsManager.AudioNotificationParameters(50, 50)
)
val incidentsList = listOf(incidentData)
IncidentsManagerProvider.getInstance().get().addIncidents(
incidentsList, object: IncidentsResultListener {
override fun onError(error: IncidentsManager.ErrorCode) {
}
override fun onSuccess() {
}
}
)