Horizon safety locations

VERSION 0.55.0

The Safety Cameras SDK for iOS 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 Safety Cameras SDK provides data for various types of safety locations, including:

Configuring the horizon engine

Having completed the Retrieving horizon data guide, you are now able to set up the horizon engine and start navigation to retrieve horizon data.

Specifying horizon options

To subscribe to safety location horizon elements, specify HorizonElementType.safetyLocationType in the list of element types of interest.

1let mainPathSearchOptions = try MainPathSearchOptions(
2 searchDistancePolicy: RouteLengthPolicy()
3)
4return try HorizonOptions(
5 elementTypes: [.safetyLocationType],
6 mainPathSearchOptions: mainPathSearchOptions
7)

Configuring navigation

The Safety Cameras feature requires configuring the safety locations data source before starting navigation. You need to create a SafetyLocationsConfiguration object and inject it into the navigation OnlineTomTomNavigationFactory.Configuration constructor. When you set up the SafetyLocationsConfiguration object, specify the API key that grants your application access to the TomTom Safety Cameras service.

In addition to configuring the safety locations data source, you also need to increase the navigation tile prefetching radius to 15 km. This ensures that enough map data is prefetched to allow exposing safety location data at the current vehicle position. With a smaller prefetching radius there is no guarantee that a zone safety location is exposed in the horizon.

1let navigationConfiguration = OnlineTomTomNavigationFactory.Configuration(
2 navigationTileStore: try NavigationTileStore(config: NavigationTileStoreConfiguration(apiKey: "YOUR_TOMTOM_API_KEY")),
3 locationProvider: locationProvider,
4 routePlanner: routePlanner,
5 safetyLocationsConfiguration: SafetyLocationsConfiguration(apiKey: "YOUR_TOMTOM_API_KEY")
6)
7
8let tomTomNavigation = try OnlineTomTomNavigationFactory.create(configuration: navigationConfiguration)

Registering an observer for horizon updates

Before starting navigation, register a NavigationHorizonObserver to listen to horizon updates for the horizon options you defined.

1let navigationHorizonObserver = SafetyLocationHorizonObserver()
2navigationHorizonObserver.updateBlock = { snapshot, position in
3 self.onHorizonUpdated(snapshot: snapshot, position: position)
4}
5
6try tomTomNavigation.addHorizonObserver(navigationHorizonObserver, options: horizonOption)

Starting navigation

Then, start navigation with a route as described in the Retrieving horizon data guide.

1let routePlan = RoutePlan(route: route, routePlanningOptions: routePlanningOptions)
2let navigationOptions = NavigationOptions(activeRoutePlan: routePlan)
3
4try tomTomNavigation.start()

Retrieving safety locations

With navigation started, you can listen to horizon updates and retrieve safety location data.

If a zone safety location stretches beyond the end of the horizon, the SDK only provides data for the part of the zone within the horizon length. As the horizon is extended ahead of the vehicle while the vehicle moves along the zone safety location, the end offset of the safety location horizon element is updated accordingly.

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. Moreover, the legislation of a country may also be ambiguous with respect to safety locations. You have to assess the situation and, if necessary, filter out safety locations for which it is illegal to issue alerts.

Crossing into another country implies complying with different legislation. To filter out safety locations from countries where reporting them is illegal, you can take the following steps:

  1. Configure HorizonOptions to also subscribe to country information in addition to safety locations:
    1let mainPathSearchOptions = try MainPathSearchOptions(
    2 searchDistancePolicy: RouteLengthPolicy()
    3)
    4return try HorizonOptions(
    5 elementTypes: [.safetyLocationType, .countryInformationType],
    6 mainPathSearchOptions: mainPathSearchOptions
    7)
  2. Implement a listener to get horizon updates and filter out safety locations:
    1func onHorizonUpdated(snapshot: HorizonSnapshot, position: HorizonPosition) {
    2 guard let mainPath = snapshot.mainPath() else { return }
    3
    4 let forbiddenCountryInformationElements = retrieveForbiddenCountryInformationElements(horizonPath: mainPath)
    5 let safetyLocationElements = retrieveSafetyLocationElements(horizonPath: mainPath)
    6
    7 let legalSafetyLocationElements = retrieveLegalSafetyLocationElements(
    8 safetyLocationElements: safetyLocationElements,
    9 forbiddenCountryInformationElements: forbiddenCountryInformationElements
    10 )
    11
    12 legalSafetyLocationElements.forEach { safetyLocation in
    13 print("Safety location: \(safetyLocation)")
    14 }
    15}
  3. Extract the country information elements that correspond to countries where reporting safety locations is illegal.
    1func retrieveForbiddenCountryInformationElements(horizonPath: HorizonPath) -> [CountryInformationElement] {
    2 return horizonPath.getElements(type: .countryInformationType)
    3 .compactMap { $0 as? CountryInformationElement }
    4 .filter {
    5 // Allows only country information elements from Germany or the US state of California
    6 ($0.countryCodeISO3 == "DEU") ||
    7 ($0.countryCodeISO3 == "USA" && $0.regionCode == "CA")
    8 }
    9}
  4. Filter out the safety locations from those countries:
    1func retrieveLegalSafetyLocationElements(
    2 safetyLocationElements: [SafetyLocationElement],
    3 forbiddenCountryInformationElements: [CountryInformationElement]
    4) -> [SafetyLocationElement] {
    5 return safetyLocationElements.filter { safetyLocation in
    6 !forbiddenCountryInformationElements.contains { countryInformation in
    7 isInCountry(safetyLocationElement: safetyLocation, countryInformationElement: countryInformation)
    8 }
    9 }
    10}
    11
    12func isInCountry(
    13 safetyLocationElement: SafetyLocationElement,
    14 countryInformationElement: CountryInformationElement
    15)
    16 -> Bool {
    17 if safetyLocationElement.pathID == countryInformationElement.pathID {
    18 return safetyLocationElement.startOffset >= countryInformationElement.startOffset &&
    19 safetyLocationElement.startOffset < countryInformationElement.endOffset ||
    20 safetyLocationElement.endOffset > countryInformationElement.startOffset &&
    21 safetyLocationElement.endOffset <= countryInformationElement.endOffset
    22 } else {
    23 return false
    24 }
    25}

You can also filter out specific safety location types:

1func retrieveSafetyLocationElements(horizonPath: HorizonPath) -> [SafetyLocationElement] {
2 return horizonPath.getElements(type: .safetyLocationType)
3 .compactMap { $0 as? SafetyLocationElement }
4 .filter {
5 // Filters out safety locations of type RestrictionCamera
6 $0.safetyLocation.type != .restrictionCamera
7 }
8}