GuidesFlutter API ReferencesHERE SDK for Android API referencesHERE SDK for iOS API references
Guides

Traffic Engine

With the TrafficEngine, you can query detailed and localizable information on current traffic incidents such as accidents, construction works or road closures.

You can visualize traffic incidents on the map by enabling the map layer state TRAFFIC_INCIDENTS with one line of code. The HERE SDK supports also a separate layer to see the current traffic situation by adding a TRAFFIC_FLOW layer.

In addition, you can also indicate the traffic along a Route instance as shown in the Visualize traffic on routes section.

Data queried with the TrafficEngine is based on the HERE Traffic API v7.

Note

For information about the pricing of this feature, see the HERE Base Plan Pricing. If you are using the Navigate license or have other questions about pricing, contact us.

Initialize the TrafficEngine

With the TrafficEngine, you can retrieve detailed information about ongoing traffic incidents. You can search for incidents along a route via a GeoCorridor, in a GeoBox or in a GeoCircle.

You can also set multiple options, for example, to search only for specific types of incidents. As a result you get results on the affected vehicle types, date information, location information, localizable descriptions, summaries and much more.

Start by creating a new instance of the TrafficEngine:

try {
    trafficEngine = new TrafficEngine();
} catch (InstantiationErrorException e) {
    throw new RuntimeException("Initialization of TrafficEngine failed: " + e.error.name());
}
try {
    trafficEngine = TrafficEngine()
} catch (e: InstantiationErrorException) {
    throw RuntimeException("Initialization of TrafficEngine failed: " + e.error.name)
}

Initiate traffic requests

Below we search inside a GeoCircle for possible TrafficIncidents:

private void queryForIncidents(GeoCoordinates centerCoords) {
    int radiusInMeters = 1000;
    GeoCircle geoCircle = new GeoCircle(centerCoords, radiusInMeters);
    TrafficIncidentsQueryOptions trafficIncidentsQueryOptions = new TrafficIncidentsQueryOptions();
    // Optionally, specify a language:
    // the language of the country where the incident occurs is used.
    // trafficIncidentsQueryOptions.languageCode = LanguageCode.EN_US;
    trafficEngine.queryForIncidents(geoCircle, trafficIncidentsQueryOptions, new TrafficIncidentsQueryCallback() {
        @Override
        public void onTrafficIncidentsFetched(@Nullable TrafficQueryError trafficQueryError,
                                              @Nullable List<TrafficIncident> trafficIncidentsList) {
            if (trafficQueryError == null) {
                // If error is null, it is guaranteed that the list will not be null.
                String trafficMessage = "Found " + trafficIncidentsList.size() + " result(s). See log for details.";
                TrafficIncident nearestIncident =
                        getNearestTrafficIncident(centerCoords, trafficIncidentsList);
                if (nearestIncident != null) {
                    trafficMessage += " Nearest incident: " + nearestIncident.getDescription().text;
                }
                showDialog("Nearby traffic incidents", trafficMessage);

                for (TrafficIncident trafficIncident : trafficIncidentsList) {
                    Log.d(TAG, "" + trafficIncident.getDescription().text);
                }
            } else {
                showDialog("TrafficQueryError:", trafficQueryError.toString());
            }
        }
    });
}
private fun queryForIncidents(centerCoords: GeoCoordinates) {
    val radiusInMeters = 1000
    val geoCircle = GeoCircle(centerCoords, radiusInMeters.toDouble())
    val trafficIncidentsQueryOptions = TrafficIncidentsQueryOptions()
    // Optionally, specify a language:
    // the language of the country where the incident occurs is used.
    // trafficIncidentsQueryOptions.languageCode = LanguageCode.EN_US;
    trafficEngine.queryForIncidents(geoCircle, trafficIncidentsQueryOptions,
        TrafficIncidentsQueryCallback { trafficQueryError, trafficIncidentsList ->
            if (trafficQueryError == null) {
                // If error is null, it is guaranteed that the list will not be null.
                var trafficMessage = "Found " + trafficIncidentsList!!.size + " result(s). See log for details."
                val nearestIncident: TrafficIncident =
                    getNearestTrafficIncident(centerCoords, trafficIncidentsList)
                if (nearestIncident != null) {
                    trafficMessage += " Nearest incident: " + nearestIncident.description.text
                }
                showDialog("Nearby traffic incidents", trafficMessage!!)

                for (trafficIncident in trafficIncidentsList) {
                    Log.d(TAG, "" + trafficIncident.description.text)
                }
            } else {
                showDialog("TrafficQueryError:", trafficQueryError.toString())
            }
        })
}

Handle traffic responses

For each found TrafficIncident, the app logs its description. If you search in Germany, by default the language of the results is German.

You can also specify another language, for example, by setting this:

trafficIncidentsQueryOptions.languageCode = LanguageCode.EN_US;
trafficIncidentsQueryOptions.languageCode = LanguageCode.EN_US;

Note that TRAFFIC_INCIDENTS is a layer that renders traffic icons and lines to indicate the location of incidents.

Each TrafficIncident contains a GeoPolyline that indicates its location. By comparing a tapped location on the map with the geographic coordinates contained in the polyline, you can find the closest incident to a tapped location on the map:

@Nullable
private TrafficIncident getNearestTrafficIncident(GeoCoordinates currentGeoCoords,
                                                  List<TrafficIncident> trafficIncidentsList) {
    if (trafficIncidentsList.size() == 0) {
        return null;
    }

    // By default, traffic incidents results are not sorted by distance.
    double nearestDistance = Double.MAX_VALUE;
    TrafficIncident nearestTrafficIncident = null;
    for (TrafficIncident trafficIncident : trafficIncidentsList) {
        List<GeoPolyline> trafficIncidentPolyLines = new ArrayList<>();

        // It is guaranteed that each incident has a valid polyline.
        trafficIncidentPolyLines.add(trafficIncident.getLocation().polyline);

        // If the polyline contains any gaps, they are available as additionalPolylines in TrafficLocation.
        trafficIncidentPolyLines.addAll(trafficIncident.getLocation().additionalPolylines);

        for (GeoPolyline trafficIncidentPolyline : trafficIncidentPolyLines) {
            for (GeoCoordinates geoCoords : trafficIncidentPolyline.vertices) {
                double currentDistance = currentGeoCoords.distanceTo(geoCoords);
                if (currentDistance < nearestDistance) {
                    nearestDistance = currentDistance;
                    nearestTrafficIncident = trafficIncident;
                }
            }
        }
    }

    return nearestTrafficIncident;
}
private fun getNearestTrafficIncident(
    currentGeoCoords: GeoCoordinates,
    trafficIncidentsList: List<TrafficIncident>?
): TrafficIncident? {
    if (trafficIncidentsList!!.size == 0) {
        return null
    }

    // By default, traffic incidents results are not sorted by distance.
    var nearestDistance = Double.MAX_VALUE
    var nearestTrafficIncident: TrafficIncident? = null
    for (trafficIncident in trafficIncidentsList) {
        val trafficIncidentPolyLines: MutableList<GeoPolyline> = java.util.ArrayList()

        // It is guaranteed that each incident has a valid polyline.
        trafficIncidentPolyLines.add(trafficIncident.location.polyline)

        // If the polyline contains any gaps, they are available as additionalPolylines in TrafficLocation.
        trafficIncidentPolyLines.addAll(trafficIncident.location.additionalPolylines)

        for (trafficIncidentPolyline in trafficIncidentPolyLines) {
            for (geoCoords in trafficIncidentPolyline.vertices) {
                val currentDistance = currentGeoCoords.distanceTo(geoCoords)
                if (currentDistance < nearestDistance) {
                    nearestDistance = currentDistance
                    nearestTrafficIncident = trafficIncident
                }
            }
        }
    }

    return nearestTrafficIncident
}

Of course, it is also possible to render the polyline of an incident by yourself on the map, by adding a MapPolyline. You can also add your own icons as MapMarker - depending on the type of the incident you may decide to choose a different image.

Query traffic flow information

With the TrafficEngine you can also query information on realtime traffic jams by calling trafficEngine.queryForFlow(...) to make an asynchronous request. Note that the data might differ from the data shown in the TRAFFIC_FLOW layer as the time of the request may happen at a different time and may be visualized differently on app-side.

private void requestRealtimeTrafficOnRoute(Route route) {
    // We are interested to see traffic also for side paths.
    int halfWidthInMeters = 500;

    GeoCorridor geoCorridor = new GeoCorridor(route.getGeometry().vertices, halfWidthInMeters);
    TrafficFlowQueryOptions trafficFlowQueryOptions = new TrafficFlowQueryOptions();
    trafficEngine.queryForFlow(geoCorridor, trafficFlowQueryOptions, new TrafficFlowQueryCallback() {
        @Override
        public void onTrafficFlowFetched(@Nullable TrafficQueryError trafficQueryError,
                                         @Nullable List<TrafficFlow> list) {
            if (trafficQueryError == null) {
                for (TrafficFlow trafficFlow : list) {
                    Double confidence = trafficFlow.getConfidence();
                    if (confidence != null && confidence <= 0.5) {
                        // Exclude speed-limit data and include only real-time and historical
                        // flow information.
                        continue;
                    }

                    // Visualize all polylines unfiltered as we get them from the TrafficEngine.
                    GeoPolyline trafficGeoPolyline = trafficFlow.getLocation().polyline;
                    addTrafficPolylines(trafficFlow.getJamFactor(), trafficGeoPolyline);
                }
            } else {
                showDialog("Error while fetching traffic flow:", trafficQueryError.toString());
            }
        }
    });
}
private fun requestRealtimeTrafficOnRoute(route: Route) {
    // We are interested to see traffic also for side paths.
    val halfWidthInMeters = 500

    val geoCorridor = GeoCorridor(route.geometry.vertices, halfWidthInMeters)
    val trafficFlowQueryOptions = TrafficFlowQueryOptions()
    trafficEngine.queryForFlow(geoCorridor, trafficFlowQueryOptions,
        TrafficFlowQueryCallback { trafficQueryError, list ->
            if (trafficQueryError == null) {
                for (trafficFlow in list!!) {
                    val confidence = trafficFlow.confidence
                    if (confidence != null && confidence <= 0.5) {
                        // Exclude speed-limit data and include only real-time and historical
                        // flow information.
                        continue
                    }

                    // Visualize all polylines unfiltered as we get them from the TrafficEngine.
                    val trafficGeoPolyline = trafficFlow.location.polyline
                    addTrafficPolylines(trafficFlow.jamFactor, trafficGeoPolyline)
                }
            } else {
                showDialog("Error while fetching traffic flow:", trafficQueryError.toString())
            }
        })
}

As a next step, you can render the traffic flow polylines using your own color encoding:

private void addTrafficPolylines(double jamFactor, GeoPolyline geoPolyline) {
    Color lineColor = getTrafficColor(jamFactor);
    if (lineColor == null) {
        // We skip rendering low traffic.
        return;
    }
    float widthInPixels = 10;
    MapPolyline trafficSpanMapPolyline = null;
    try {
        trafficSpanMapPolyline = new MapPolyline(geoPolyline, new MapPolyline.SolidRepresentation(
                new MapMeasureDependentRenderSize(RenderSize.Unit.PIXELS, widthInPixels),
                lineColor,
                LineCap.ROUND));
    }  catch (MapPolyline.Representation.InstantiationException e) {
        Log.e("MapPolyline Representation Exception:", e.error.name());
    } catch (MapMeasureDependentRenderSize.InstantiationException e) {
        Log.e("MapMeasureDependentRenderSize Exception:", e.error.name());
    }

    mapView.getMapScene().addMapPolyline(trafficSpanMapPolyline);
    mapPolylines.add(trafficSpanMapPolyline);
}

// Define a traffic color scheme based on the traffic jam factor.
// 0 <= jamFactor < 4: No or light traffic.
// 4 <= jamFactor < 8: Moderate or slow traffic.
// 8 <= jamFactor < 10: Severe traffic.
// jamFactor = 10: No traffic, ie. the road is blocked.
// Returns null in case of no or light traffic.
@Nullable
private Color getTrafficColor(Double jamFactor) {
    if (jamFactor == null || jamFactor < 4) {
        return null;
    } else if (jamFactor >= 4 && jamFactor < 8) {
        return Color.valueOf(1, 1, 0, 0.63f); // Yellow
    } else if (jamFactor >= 8 && jamFactor < 10) {
        return Color.valueOf(1, 0, 0, 0.63f); // Red
    }
    return Color.valueOf(0, 0, 0, 0.63f); // Black
}
private fun addTrafficPolylines(jamFactor: Double, geoPolyline: GeoPolyline) {
    val lineColor = getTrafficColor(jamFactor)
        ?: // We skip rendering low traffic.
        return
    val widthInPixels = 10f
    var trafficSpanMapPolyline: MapPolyline? = null
    try {
        trafficSpanMapPolyline = MapPolyline(
            geoPolyline, SolidRepresentation(
                MapMeasureDependentRenderSize(RenderSize.Unit.PIXELS, widthInPixels.toDouble()),
                lineColor,
                LineCap.ROUND
            )
        )
    } catch (e: MapPolyline.Representation.InstantiationException) {
        Log.e("MapPolyline Representation Exception:", e.error.name)
    } catch (e: MapMeasureDependentRenderSize.InstantiationException) {
        Log.e("MapMeasureDependentRenderSize Exception:", e.error.name)
    }

    mapView.mapScene.addMapPolyline(trafficSpanMapPolyline!!)
    mapPolylines.add(trafficSpanMapPolyline)
}

// Define a traffic color scheme based on the traffic jam factor.
// 0 <= jamFactor < 4: No or light traffic.
// 4 <= jamFactor < 8: Moderate or slow traffic.
// 8 <= jamFactor < 10: Severe traffic.
// jamFactor = 10: No traffic, ie. the road is blocked.
// Returns null in case of no or light traffic.
@Nullable
private fun getTrafficColor(jamFactor: Double?): Color? {
    if (jamFactor == null || jamFactor < 4) {
        return null
    } else if (jamFactor >= 4 && jamFactor < 8) {
        return Color.valueOf(1f, 1f, 0f, 0.63f) // Yellow
    } else if (jamFactor >= 8 && jamFactor < 10) {
        return Color.valueOf(1f, 0f, 0f, 0.63f) // Red
    }
    return Color.valueOf(0f, 0f, 0f, 0.63f) // Black
}

Try the Traffic example app

A usage example is available on GitHub as part of the "Traffic" example app, provided in both Java and Kotlin.