Skip to content

Traffic issue #501

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added .screenshots/Screenshot_20221122_195412.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added .screenshots/Screenshot_20221122_195701.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added .screenshots/Screenshot_20221122_200055.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added .screenshots/Screenshot_20221122_200606.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added .screenshots/Screenshot_20221122_200812.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
245 changes: 245 additions & 0 deletions TRAFFIC.MD

Large diffs are not rendered by default.

13 changes: 13 additions & 0 deletions routing/app/build.gradle
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'

android {
compileSdkVersion 33
Expand All @@ -24,4 +25,16 @@ dependencies {
implementation files('libs/HERE-sdk.aar')
implementation 'com.google.code.gson:gson:2.8.2'
implementation 'androidx.appcompat:appcompat:1.2.0'

implementation 'androidx.appcompat:appcompat:1.5.1'
implementation "androidx.core:core-ktx:1.9.0"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.1"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.1"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.6.1"


annotationProcessor "androidx.lifecycle:lifecycle-compiler:2.4.1"
implementation "androidx.lifecycle:lifecycle-common-java8:2.4.1"
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.4.1"
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,21 +18,28 @@

import android.app.AlertDialog;
import android.content.DialogInterface;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;

import androidx.appcompat.app.AppCompatActivity;

import com.arrival.vehiclenavigation.R;
import com.here.android.example.routing.traffic.RouteTrafficInfo;
import com.here.android.example.routing.traffic.TrafficRepository;
import com.here.android.mpa.common.GeoCoordinate;
import com.here.android.mpa.common.GeoPolygon;
import com.here.android.mpa.common.OnEngineInitListener;
import com.here.android.mpa.common.RoadElement;
import com.here.android.mpa.mapping.AndroidXMapFragment;
import com.here.android.mpa.mapping.Map;
import com.here.android.mpa.mapping.MapRoute;
import com.here.android.mpa.mapping.MapTrafficLayer;
import com.here.android.mpa.mapping.TrafficEvent;
import com.here.android.mpa.routing.CoreRouter;
import com.here.android.mpa.routing.DrivingDirection;
import com.here.android.mpa.routing.DynamicPenalty;
Expand All @@ -45,12 +52,18 @@
import com.here.android.mpa.routing.RoutingError;
import com.here.android.mpa.routing.RoutingZone;

import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.EnumSet;
import java.util.Iterator;
import java.util.List;

import kotlin.Unit;
import kotlin.jvm.functions.Function1;

/**
* This class encapsulates the properties and functionality of the Map view.A route calculation from
* south of Berlin to the north of Berlin.
Expand All @@ -64,8 +77,10 @@ public class MapFragmentView {
private AppCompatActivity m_activity;
private Map m_map;
private MapRoute m_mapRoute;
private TextView m_trafficReport;
private boolean m_isExcludeRoutingZones;
private boolean m_addAvoidedAreas;
private NumberFormat decimalFormat = DecimalFormat.getInstance();

public MapFragmentView(AppCompatActivity activity) {
m_activity = activity;
Expand All @@ -74,6 +89,8 @@ public MapFragmentView(AppCompatActivity activity) {
* We use a button in this example to control the route calculation
*/
initCreateRouteButton();
m_trafficReport = m_activity.findViewById(R.id.trafficReport);
decimalFormat.setMaximumFractionDigits(2);
}

private AndroidXMapFragment getMapFragment() {
Expand All @@ -93,6 +110,11 @@ public void onEngineInitializationCompleted(OnEngineInitListener.Error error) {
if (error == Error.NONE) {
/* get the map object */
m_map = m_mapFragment.getMap();
m_map.getMapTrafficLayer().setEnabled(MapTrafficLayer.RenderLayer.FLOW, true);
m_map.getMapTrafficLayer().setEnabled(MapTrafficLayer.RenderLayer.INCIDENT, true);
m_map.getMapTrafficLayer().setEnabled(MapTrafficLayer.RenderLayer.ONROUTE, true);

m_map.setTrafficInfoVisible(true);

/*
* Set the map center to the south of Berlin.
Expand Down Expand Up @@ -187,9 +209,9 @@ private void createRoute(final List<RoutingZone> excludedRoutingZones) {

/* Define waypoints for the route */
/* START: South of Berlin */
RouteWaypoint startPoint = new RouteWaypoint(new GeoCoordinate(52.406425, 13.193975));
RouteWaypoint startPoint = new RouteWaypoint(new GeoCoordinate(52.505326, 13.368087));
/* END: North of Berlin */
RouteWaypoint destination = new RouteWaypoint(new GeoCoordinate(52.638623, 13.441998));
RouteWaypoint destination = new RouteWaypoint(new GeoCoordinate(52.491863, 13.377296));

/* Add both waypoints to the route plan */
routePlan.addWaypoint(startPoint);
Expand Down Expand Up @@ -218,6 +240,8 @@ public void onCalculateRouteFinished(List<RouteResult> routeResults,
} else {
/* Create a MapRoute so that it can be placed on the map */
m_mapRoute = new MapRoute(route);
m_mapRoute.setTrafficEnabled(true);
loadTrafficForRoute(route);

/* Show the maneuver number on top of the route */
m_mapRoute.setManeuverNumberVisible(true);
Expand All @@ -241,6 +265,34 @@ public void onCalculateRouteFinished(List<RouteResult> routeResults,
});
}

public void loadTrafficForRoute(Route route) {
TrafficRepository.INSTANCE.loadTrafficForRoute(m_activity, route, new Function1<List<? extends RouteTrafficInfo>, Unit>() {
@Override
public Unit invoke(List<? extends RouteTrafficInfo> routeTrafficInfos) {
Log.d("MapFragmentView", "loadTrafficForRoute " + routeTrafficInfos.toString());

StringBuilder builder = new StringBuilder();
Iterator<? extends RouteTrafficInfo> i = routeTrafficInfos.iterator();
while (i.hasNext()) {
RouteTrafficInfo v = i.next();
if(v.getTrafficSeverity() == TrafficEvent.Severity.NORMAL) continue;
builder.append("s:");
builder.append(decimalFormat.format(v.getStartPercent()));
builder.append(" e:");
builder.append(decimalFormat.format(v.getEndPercent()));
builder.append(" l:");
builder.append(decimalFormat.format(v.getEndPercent() - v.getStartPercent()));
builder.append(" level: ");
builder.append(v.getTrafficSeverity());
builder.append("\n");
}
m_trafficReport.setText(builder);
Log.d("MapFragmentView", "loadTrafficForRoute " + builder.toString());
return Unit.INSTANCE;
}
});
}

public boolean onOptionsItemSelected(MenuItem item) {
item.setChecked(!item.isChecked());
if (item.getItemId() == ITEM_ID_SHOW_ZONES) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
package com.here.android.example.routing.traffic

import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
import com.here.android.mpa.common.Identifier
import com.here.android.mpa.common.RoadElement
import com.here.android.mpa.guidance.TrafficUpdater
import com.here.android.mpa.mapping.TrafficEvent
import com.here.android.mpa.mapping.TrafficEvent.Severity
import com.here.android.mpa.routing.Route
import com.here.android.mpa.routing.RouteElement
import com.here.android.mpa.routing.RouteElements
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.withContext
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
import kotlin.coroutines.suspendCoroutine
import kotlin.math.roundToLong

data class RouteTrafficInfo(
val startPercent: Float,
val endPercent: Float,
val trafficSeverity: Severity
)

data class RelativeTrafficInfoMeters(
val roadElement: RoadElement,
val start: Long,
val length: Long,
val trafficSeverity: Severity,
)

object TrafficRepository {


private val trafficUpdater by lazy { TrafficUpdater.getInstance() }
private var job: Job? = null

fun loadTrafficForRoute(activity: AppCompatActivity, route: Route, block: (List<RouteTrafficInfo>) -> Unit) {
job?.cancel()
job = activity.lifecycleScope.launch {
var routeTrafficInfoList = getTrafficForRoute(route, false)

withContext(Dispatchers.Main) {
block(routeTrafficInfoList)
}

while (isActive) {
delay(15_000L)
routeTrafficInfoList = getTrafficForRoute(route, true)

withContext(Dispatchers.Main) {
block(routeTrafficInfoList)
}
}
}
}

fun cancelRequest() {
job?.cancel()
}

private suspend fun getTrafficForRoute(
route: Route,
forceRequest: Boolean
): List<RouteTrafficInfo> = try {
if (forceRequest) {
route.routeElements?.let {
requestTrafficUpdate(it)
}
}
val trafficEventList = fetchTrafficEventList(route)
val routeElements = route.routeElements
if (routeElements != null ) {
val affectedRouteElements = routeElements.elements
.getRouteRangesTraffic()
.applyTrafficEvents(trafficEventList.filter { it.isOnRoute(route) })

val packedTrafficInfo = affectedRouteElements.values.toList()
.pack()
.toInterval(route.length.toLong())

packedTrafficInfo
} else {
emptyList()
}
} catch (ex: CancellationException) {
throw ex
} catch (ex: Exception) {
Log.e(this.javaClass.name, "getTrafficForRoute", ex)
emptyList()
}

private suspend fun fetchTrafficEventList(route: Route): List<TrafficEvent> =
suspendCoroutine { continuation ->
val listener = TrafficUpdater.GetEventsListener { eventsList, error ->
if (error != TrafficUpdater.Error.NONE) {
Log.e(this.javaClass.name, "fetchTrafficEventList $error")
continuation.resumeWith(Result.success(emptyList()))
} else {
Log.v(this.javaClass.name, "fetchTrafficEventList ${eventsList.filter { it.severity != TrafficEvent.Severity.NORMAL }}")
continuation.resumeWith(Result.success(eventsList))
}
}
trafficUpdater.getEvents(route, listener)
}

private suspend fun requestTrafficUpdate(routeElements: RouteElements): TrafficUpdater.RequestState =
suspendCancellableCoroutine { continuation ->
val listener = TrafficUpdater.Listener { requestState ->
continuation.resume(requestState)
}

val requestInfo = trafficUpdater.request(routeElements, listener)

if (requestInfo.error != TrafficUpdater.Error.NONE)
continuation.resumeWithException(
IllegalStateException("an error occurred while requesting ${requestInfo.error}}")
)
continuation.invokeOnCancellation {
trafficUpdater.cancelRequest(requestInfo.requestId)
}
}

/**
* Maps ALL current route RoadElements to RelativeTrafficInfoMeters
*/
private fun List<RouteElement>.getRouteRangesTraffic(): MutableMap<Identifier, RelativeTrafficInfoMeters> {
val internalRoadElements = this.mapNotNull { it.roadElement }

val map: MutableMap<Identifier, RelativeTrafficInfoMeters> = HashMap()
var lastFinish = 0L
internalRoadElements
.map { Triple(it, it.identifier, it.geometryLength.roundToLong()) }
.forEach { (routeElement, id, length) ->
id?.let { itemId ->
map[itemId] =
RelativeTrafficInfoMeters(
routeElement,
lastFinish,
length,
TrafficEvent.Severity.NORMAL
)
lastFinish += length
}
}
return map
}

/**
* Applies traffic severity info to route road elements
*/
private fun MutableMap<Identifier, RelativeTrafficInfoMeters>.applyTrafficEvents(eventsList: List<TrafficEvent>): Map<Identifier, RelativeTrafficInfoMeters> {
eventsList.filter { it.severity != TrafficEvent.Severity.NORMAL }
.flatMap { event ->
event.affectedRoadElements.map { roadElement ->
Pair(
roadElement.identifier,
event.severity
)
}
}
.forEach { (id, severity) ->
id?.let { itemId ->
this[itemId]?.copy(trafficSeverity = severity)?.let {
this[itemId] = it
}
}
}
return this.toMap()
}

/**
* Merge sibling [RelativeTrafficInfoMeters] elements anc calculate merged length.
*/
private fun List<RelativeTrafficInfoMeters>.pack(): List<RelativeTrafficInfoMeters> {
val packedTrafficList = emptyList<RelativeTrafficInfoMeters>().toMutableList()
this.sortedBy { it.start }
.forEach {
if (packedTrafficList.isEmpty())
packedTrafficList.add(it)
else {
val last = packedTrafficList.last()
if (last.trafficSeverity == it.trafficSeverity) {
packedTrafficList.removeLast()
// merge lengths
packedTrafficList.add(last.copy(length = last.length + it.length))
} else {
packedTrafficList.add(it)
}
}
}

return packedTrafficList
}

private fun List<RelativeTrafficInfoMeters>.toInterval(routeLength: Long): List<RouteTrafficInfo> {
return this.map {
RouteTrafficInfo(
it.start.fractionForRange(0, routeLength),
(it.start + it.length).fractionForRange(0, routeLength),
it.trafficSeverity
)
}
}

fun Long.fractionForRange(start: Long, end: Long): Float =
coerceIn(start, end).let { (it - start) / (end - start).toFloat() }
}

7 changes: 7 additions & 0 deletions routing/app/src/main/res/layout/activity_main.xml
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,11 @@
android:layout_height="wrap_content"
android:text="Create Route"
android:id="@+id/button" />

<TextView
android:id="@+id/trafficReport"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textStyle="bold"
android:layout_alignParentBottom="true"/>
</RelativeLayout>
Loading