Horizon safety locations

VERSION 0.45.0
PUBLIC PREVIEW

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 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 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)

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 SafetyLocationsConfiguration object and inject it into into the navigation Configuration constructor. When you create the SafetyLocationsConfiguration object, you need to specify an API key that grants your application access to the TomTom Safety Cameras service.

1let navigationConfiguration = OnlineTomTomNavigationFactory.Configuration(
2 locationProvider: locationProvider,
3 routeReplanner: routeReplanner,
4 apiKey: "YOUR_API_KEY",
5 safetyLocationsConfiguration: SafetyLocationsConfiguration(apiKey: "YOUR_API_KEY")
6)
7
8let tomTomNavigation = try OnlineTomTomNavigationFactory.create(configuration: navigationConfiguration)

To retrieve safety locations from the horizon engine, declare a custom NavigationHorizonObserver.

1class SafetyLocationHorizonObserver: NavigationHorizonObserver {
2 private var snapshot: HorizonSnapshot?
3 private var position: HorizonPosition?
4
5 var updateBlock: ((HorizonSnapshot, HorizonPosition) -> ())?
6
7 func didUpdateSnapshot(options: HorizonOptions, snapshot: HorizonSnapshot) {
8 self.snapshot = snapshot
9 }
10
11 func didUpdatePosition(options: HorizonOptions, position: HorizonPosition) {
12 self.position = position
13
14 if let snapshot {
15 updateBlock?(snapshot, position)
16 }
17 }
18
19 func didResetHorizon(options: HorizonOptions) {
20 // do nothing
21 }
22}

Register the observer when starting navigation.

1let navigationHorizonObserver = SafetyLocationHorizonObserver()
2navigationHorizonObserver.updateBlock = { snapshot, position in
3 self.onHorizonUpdated(snapshot: snapshot, position: position)
4}
5
6try tomTomNavigation.addHorizonObserver(navigationHorizonObserver, options: horizonOption)
7
8let routePlan = RoutePlan(route: route, routePlanningOptions: routePlanningOptions)
9let navigationOptions = NavigationOptions(activeRoutePlan: routePlan)
10
11try tomTomNavigation.start()

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:

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

With navigation started, you can listen to horizon updates to retrieve and filter 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}

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

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}

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

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}

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

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}