Horizon safety locations
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:
Red light camerasFixed speed camerasMobile speed camerasSpeed enforcement zonesRestricted areasDanger zonesRisk zones
Configuring horizon
Having completed the Retrieving horizon data guide, you are now able to subscribe for horizon updates and start navigation to retrieve horizon data.
Specifying horizon options
To subscribe to safety location horizon elements, create HorizonOptions via the buildHorizonOptions method and specify SafetyLocationElementType in the list of element types of interest. The horizon path parameters defined within buildHorizonOptions are appropriate for most use cases and can be utilized in both free driving and active guidance scenarios. The buildHorizonOptions method will create a HorizonOptions instance with a main path search distance that is the minimum between 25 km and the remaining route length when navigating with a route, or 10 km when free driving.
val horizonOptions = buildHorizonOptions(listOf(SafetyLocationElementType))When using the Extended flavor
If you prefer to configure the horizon path parameters manually, you can create the HorizonOptions as follows:
val horizonOptions = HorizonOptions( elementTypes = listOf(SafetyLocationElementType), mainPathSearchOptions = MainPathSearchOptions( searchDistancePolicy = ExplicitDistancePolicy( searchDistance = PathSearchDistance(maxHorizonLength = Distance.kilometers(2)), ), ), subPathSearchOptions = listOf( SubPathSearchOptions( searchDistance = PathSearchDistance(maxHorizonLength = Distance.meters(100)), ), ), numberOfPaths = 10, )Registering a horizon updated listener
Before starting navigation, register a HorizonUpdatedListener to listen to horizon updates for the horizon options you defined.
tomTomNavigation.addHorizonUpdatedListener(horizonOptions, horizonUpdatedListener)Starting navigation
Then, start navigation with a route as described in the Retrieving horizon data guide.
val routePlan = RoutePlan(route, routePlanningOptions)val navigationOptions = NavigationOptions(routePlan)tomTomNavigation.start(navigationOptions)Retrieving safety locations
With navigation started, you can listen to horizon updates and retrieve safety location data.
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:
- Configure
HorizonOptionsto also subscribe tocountry informationin addition tosafety locations:val horizonOptions =buildHorizonOptions(listOf(SafetyLocationElementType, CountryInformationElementType)) - Implement a listener to get horizon updates and filter out safety locations:
private val horizonUpdatedListener =object : HorizonUpdatedListener {private var horizonSnapshot: HorizonSnapshot? = nulloverride fun onPositionUpdated(options: HorizonOptions,position: HorizonPosition,) {horizonSnapshot?.let { horizonSnapshot ->horizonSnapshot.mainPath()?.let { mainPath ->val forbiddenCountryInformationElements =mainPath.retrieveForbiddenCountryInformationElements()val safetyLocationElements =mainPath.retrieveSafetyLocationElements()val legalSafetyLocationElements =retrieveLegalSafetyLocationElements(safetyLocationElements,forbiddenCountryInformationElements,)Log.v(TAG, "Safety locations for which alerts are legal:")legalSafetyLocationElements.forEach { safetyLocation ->Log.v(TAG, "$safetyLocation")}getRemainingDistanceInZone(safetyLocationElements, position)logAverageSpeedInAverageSpeedZone(safetyLocationElements, position)}}}override fun onHorizonReset(options: HorizonOptions) {Log.v(TAG, "Horizon reset")}override fun onSnapshotUpdated(options: HorizonOptions,snapshot: HorizonSnapshot,) {horizonSnapshot = snapshot}
- Extract the
country information elementsthat correspond to countries where reporting safety locations is illegal.private fun HorizonPath.retrieveForbiddenCountryInformationElements(): List<CountryInformationElement> =getElements(CountryInformationElementType).map { it as CountryInformationElement }.filter {// Allows only country information elements from Germany or the US state of California(it.countryCodeIso3 == "DEU") ||(it.countryCodeIso3 == "USA" && it.regionCode == "CA")} - Filter out the
safety locationsfrom those countries:private fun retrieveLegalSafetyLocationElements(safetyLocationElements: List<SafetyLocationElement>,forbiddenCountryInformationElements: List<CountryInformationElement>,): List<SafetyLocationElement> = safetyLocationElements.filterNot { safetyLocationElement ->forbiddenCountryInformationElements.map { countryInformationElement ->safetyLocationElement.isInCountry(countryInformationElement)}.any { it }}private fun SafetyLocationElement.isInCountry(countryInformationElement: CountryInformationElement,): Boolean = if (this.pathId == countryInformationElement.pathId) {this.startOffset >= countryInformationElement.startOffset &&this.startOffset < countryInformationElement.endOffset ||this.endOffset > countryInformationElement.startOffset &&this.endOffset <= countryInformationElement.endOffset} else {false}
You can also filter out specific safety location types:
private fun HorizonPath.retrieveSafetyLocationElements(): List<SafetyLocationElement> = getElements(SafetyLocationElementType) .map { it as SafetyLocationElement } .filter { element -> // Filters out safety locations of type RestrictionCamera element.safetyLocation.type != SafetyLocationType.RestrictionCamera }Understanding the isFinal state of safety location elements
The horizon path extends ahead as the vehicle progresses. Depending on the configured horizon search distance and the safety location zone’s own length, it might initially stretch beyond the end of the horizon path. If this is the case, the horizon snapshot will include only part of the safety location zone. As the vehicle moves along the zone, the horizon path continues to extend, overlapping a gradually increasing section of the zone. The end offset and the geometry of the safety location element are updated dynamically and reflect the extent to which the current horizon snapshot covers the zone. The isFinal flag indicates when this dynamic updating stops. It switches to true once the horizon snapshot fully covers the exit point of the safety location zone. From that moment on, the end offset and the element geometry become stable and are no longer updated.
This mechanism enables you to:
- Evaluate the remaining distance to the end of the zone. As long as
isFinalremains false, the remaining distance is greater than the configured horizon search distance. OnceisFinalbecomes true, you can compute the remaining distance as the difference between the end offset of the element and the offset of the vehicle position. - Place end marker icons on the map. While
isFinalis still false, the icon would change position frequently. To avoid that, you can choose to only display the icon afterisFinalbecomes true.
private fun getRemainingDistanceInZone( safetyLocationElements: List<SafetyLocationElement>, horizonPosition: HorizonPosition,) { safetyLocationElements .filter { it.safetyLocation.type == SafetyLocationType.AverageSpeedZone } .forEach { element -> if (horizonPosition.pathId == element.pathId && horizonPosition.offset in element.startOffset..element.endOffset ) { if (element.isFinal) { val remainingDistance = element.endOffset - horizonPosition.offset Log.v(TAG, "Remaining distance to end of zone: $remainingDistance") } } }}Average speed in average speed zones
An AverageSpeedZone is a fixed zone in which multiple cameras calculate the average speed of vehicles traveling through the zone. When a safety location of this type is present on the horizon, the Navigation SDK provides the current average speed of the vehicle from the moment it enters the zone. This value is updated in real time as new speed data is collected, and is available only while the vehicle remains inside the zone.
private fun logAverageSpeedInAverageSpeedZone( safetyLocationElements: List<SafetyLocationElement>, horizonPosition: HorizonPosition,) { safetyLocationElements .filter { it.safetyLocation.type == SafetyLocationType.AverageSpeedZone } .forEach { element -> if (horizonPosition.pathId == element.pathId && horizonPosition.offset in element.startOffset..element.endOffset ) { val averageSpeed = element.currentAverageSpeed if (averageSpeed != null) { Log.v(TAG, "Current average speed in zone: $averageSpeed") } } }}Next steps
Now that you know how to retrieve horizon safety locations, here are the recommendations on what to explore next: