Horizon safety locations

VERSION 1.0.0
PUBLIC PREVIEW

The Safety Cameras SDK for Android is only available upon request. Contact us to get started.

Legal Restrictions.

Local law in certain jurisdictions can prohibit the use of speed camera warning and detection systems. To the extent a TomTom customer uses the Speed Camera Data in connection with any such warning or detection system, the TomTom customer shall do so entirely at their own risk. TomTom shall be entitled to immediately cease the delivery of the Speed Camera Data or part thereof in the event delivery of such Speed Camera Data infringes applicable law. In order to comply with the applicable legal requirements, application developers can use the Navigation SDK to filter safety cameras with the purpose of:

  • disabling/enabling all types of safety cameras for an individual country
  • disabling/enabling all types of safety cameras for a region of a country
  • disabling/enabling some types of safety cameras (fixed/mobile) for a specific country

Safety Locations are used worldwide to enforce speed limits and other traffic regulations. Cameras are frequently placed in high-risk areas to minimize the impact of difficult and dangerous stretches of road.

The TomTom Safety Cameras service provides clients with:

Configuring the horizon engine

Having completed the Retrieving horizon data guide, you are now able to configure and set up a Virtual Horizon.

Specifying horizon options

To indicate that you are interested in safety location horizon elements, specify SafetyLocationElementType in the list of element types of interest.

1private val horizonOptionsSafetyLocations = HorizonOptions(
2 elementTypes = listOf(SafetyLocationElementType),
3 mainPathSearchOptions = MainPathSearchOptions(
4 searchDistancePolicy = RouteLengthPolicy
5 )
6)

Starting navigation

To enable the Safety Locations feature, you need to configure the safety locations data source when you start navigation. To do that, create a SafetyLocationConfiguration object and inject it into into the navigation Configuration constructor. When you create the SafetyLocationConfiguration object, you need to specify an API key that grants your application access to the TomTom Safety Cameras service.

1private lateinit var locationProvider: LocationProvider
2private lateinit var navigationTileStore: NavigationTileStore
3private lateinit var routePlanner: RoutePlanner
4
5private val navigationConfiguration = Configuration(
6 context = this,
7 locationProvider = locationProvider,
8 navigationTileStore = navigationTileStore,
9 routePlanner = routePlanner,
10 safetyLocationsConfiguration = SafetyLocationsConfiguration(SAFETY_LOCATIONS_API_KEY)
11)
12
13private val tomTomNavigation = OnlineTomTomNavigationFactory.create(navigationConfiguration)

To retrieve safety locations from the horizon engine, you have to register a HorizonUpdatedListener.

1private fun startNavigation(route: Route, routePlanningOptions: RoutePlanningOptions) {
2 tomTomNavigation.addHorizonUpdatedListener(horizonOptionsSafetyLocations, horizonUpdatedListener)
3
4 val routePlan = RoutePlan(route, routePlanningOptions)
5 val navigationOptions = NavigationOptions(routePlan)
6 tomTomNavigation.start(navigationOptions)
7}

Retrieving and filtering safety locations

Some of the retrieved safety locations might be located in a country where it is prohibited to use devices that alert the driver about safety locations. You would have to assess the situation and, if necessary, filter out these safety locations. Therefore, you must assess the risk and, if required, apply additional filtering to ignore those elements.

Crossing into another country’s jurisdiction implies complying with different legislation. The legislation of the new country may be ambiguous with respect to safety locations. To detect a border crossing, you can use the following approach:

Configure HorizonOptions to also subscribe to country information in addition safety locations:

1private val horizonOptions = HorizonOptions(
2 elementTypes = listOf(SafetyLocationElementType, CountryInformationElementType),
3 mainPathSearchOptions = MainPathSearchOptions(
4 searchDistancePolicy = RouteLengthPolicy
5 )
6)

With navigation started, you can listen to horizon updates to retrieve and filter safety locations:

1private val horizonUpdatedListener = object : HorizonUpdatedListener {
2 private var horizonSnapshot: HorizonSnapshot? = null
3
4 override fun onPositionUpdated(options: HorizonOptions, position: HorizonPosition) {
5 horizonSnapshot?.let { horizonSnapshot ->
6 horizonSnapshot.mainPath()?.let { mainPath ->
7 val forbiddenCountryInformationElements = mainPath.retrieveForbiddenCountryInformationElements()
8 val safetyLocationElements = mainPath.retrieveSafetyLocationElements()
9
10 val legalSafetyLocationElements =
11 retrieveLegalSafetyLocationElements(safetyLocationElements, forbiddenCountryInformationElements)
12
13 legalSafetyLocationElements.forEach { safetyLocation ->
14 Log.v("HorizonActivity", "Safety location: $safetyLocation")
15 }
16 }
17 }
18 }
19
20 override fun onHorizonReset(options: HorizonOptions) {
21 Log.v("HorizonActivity", "Horizon reset")
22 }
23
24 override fun onSnapshotUpdated(options: HorizonOptions, snapshot: HorizonSnapshot) {
25 horizonSnapshot = snapshot
26 }

You can filter the country information elements for a specific country:

1private fun HorizonPath.retrieveForbiddenCountryInformationElements(): List<CountryInformationElement> =
2 getElements(CountryInformationElementType)
3 .map { it as CountryInformationElement }
4 .filter {
5 // Allows only country information elements from Germany or the US state of California
6 (it.countryCodeIso3 == "DEU") ||
7 (it.countryCodeIso3 == "USA" && it.regionCode == "CA")
8 }

You can also filter the safety locations, by filtering against types that you do not want to display:

1private fun HorizonPath.retrieveSafetyLocationElements(): List<SafetyLocationElement> =
2 getElements(SafetyLocationElementType)
3 .map { it as SafetyLocationElement }
4 .filter { element ->
5 // Filters out safety locations of type RestrictionCamera
6 element.safetyLocation.type != SafetyLocationType.RestrictionCamera
7 }

Then process the retrieved horizon data to filter out safety locations from countries where reporting them is illegal:

1private fun retrieveLegalSafetyLocationElements(
2 safetyLocationElements: List<SafetyLocationElement>,
3 forbiddenCountryInformationElements: List<CountryInformationElement>
4): List<SafetyLocationElement> =
5 safetyLocationElements.filterNot { safetyLocationElement ->
6 forbiddenCountryInformationElements.map { countryInformationElement ->
7 safetyLocationElement.isInCountry(countryInformationElement)
8 }.any { it }
9 }
10
11private fun SafetyLocationElement.isInCountry(countryInformationElement: CountryInformationElement): Boolean =
12 if (this.pathId == countryInformationElement.pathId) {
13 this.startOffset >= countryInformationElement.startOffset &&
14 this.startOffset < countryInformationElement.endOffset ||
15 this.endOffset > countryInformationElement.startOffset &&
16 this.endOffset <= countryInformationElement.endOffset
17 } else {
18 false
19 }