ガイド変更履歴HERE SDK API references
ガイド

ルート逸脱を処理する

ナビゲーションはNavigateライセンスでのみ利用できます。

ルートの逸脱が検出された際に、目的地へのルートを変更してユーザーを誘導するかどうかをdistanceInMetersに基づいて決定できます。完全なルート再計算では、同じルート パラメーターを使用できます。

この仕組みは次のとおりです。

  1. 絶え間なくRouteDeviationイベントをリッスンします。
  2. メートルの偏差に基づいて、次に行う操作を決定します。例:
    1. Route を新しく作成し、以前の Route の代わりにナビゲーションに使用します。
    2. または、returnToRoute() を使用し、既存の Route を使用し続けます。
    3. 変更なしで、古い Route への追随を続けます。リスク:偏差が大きすぎると、ユーザーは次の運転操作に到達しなかったり、運転操作を失敗したりする可能性があります。

逸脱に関する通知を受信する

RouteDeviation イベントを取得する以下のコードを参照してください。

// Notifies on a possible deviation from the route.
visualNavigator.setRouteDeviationListener(new RouteDeviationListener() {
    @Override
    public void onRouteDeviation(@NonNull RouteDeviation routeDeviation) {
        Route route = visualNavigator.getRoute();
        if (route == null) {
            // May happen in rare cases when route was set to null inbetween.
            return;
        }

        // Get current geographic coordinates.
        MapMatchedLocation currentMapMatchedLocation = routeDeviation.currentLocation.mapMatchedLocation;
        GeoCoordinates currentGeoCoordinates = currentMapMatchedLocation == null ?
                routeDeviation.currentLocation.originalLocation.coordinates : currentMapMatchedLocation.coordinates;

        // Get last geographic coordinates on route.
        GeoCoordinates lastGeoCoordinatesOnRoute;
        if (routeDeviation.lastLocationOnRoute != null) {
            MapMatchedLocation lastMapMatchedLocationOnRoute = routeDeviation.lastLocationOnRoute.mapMatchedLocation;
            lastGeoCoordinatesOnRoute = lastMapMatchedLocationOnRoute == null ?
                    routeDeviation.lastLocationOnRoute.originalLocation.coordinates : lastMapMatchedLocationOnRoute.coordinates;
        } else {
            Log.d(TAG, "User was never following the route. So, we take the start of the route instead.");
            lastGeoCoordinatesOnRoute = route.getSections().get(0).getDeparturePlace().originalCoordinates;
        }

        int distanceInMeters = (int) currentGeoCoordinates.distanceTo(lastGeoCoordinatesOnRoute);
        Log.d(TAG, "RouteDeviation in meters is " + distanceInMeters);

        // Now, an application needs to decide if the user has deviated far enough and
        // what should happen next: For example, you can notify the user or simply try to
        // calculate a new route. When you calculate a new route, you can, for example,
        // take the current location as new start and keep the destination - another
        // option could be to calculate a new route back to the lastMapMatchedLocationOnRoute.
        // At least, make sure to not calculate a new route every time you get a RouteDeviation
        // event as the route calculation happens asynchronously and takes also some time to
        // complete.
        // The deviation event is sent any time an off-route location is detected: It may make
        // sense to await around 3 events before deciding on possible actions.
    }
});
// Notifies on a possible deviation from the route.
visualNavigator.routeDeviationListener =
    RouteDeviationListener { routeDeviation: RouteDeviation ->
        val route = visualNavigator.route
            ?: // May happen in rare cases when route was set to null inbetween.
            return@RouteDeviationListener
        // Get current geographic coordinates.
        val currentMapMatchedLocation = routeDeviation.currentLocation.mapMatchedLocation
        val currentGeoCoordinates = currentMapMatchedLocation?.coordinates
            ?: routeDeviation.currentLocation.originalLocation.coordinates

        // Get last geographic coordinates on route.
        val lastGeoCoordinatesOnRoute: GeoCoordinates?
        if (routeDeviation.lastLocationOnRoute != null) {
            val lastMapMatchedLocationOnRoute =
                routeDeviation.lastLocationOnRoute!!.mapMatchedLocation
            lastGeoCoordinatesOnRoute = lastMapMatchedLocationOnRoute?.coordinates
                ?: routeDeviation.lastLocationOnRoute!!.originalLocation.coordinates
        } else {
            Log.d(
                TAG,
                "User was never following the route. So, we take the start of the route instead."
            )
            lastGeoCoordinatesOnRoute = route.sections[0].departurePlace.originalCoordinates
        }

        val distanceInMeters = currentGeoCoordinates.distanceTo(
            lastGeoCoordinatesOnRoute!!
        ).toInt()
        Log.d(
            TAG,
            "RouteDeviation in meters is $distanceInMeters"
        )

        // Now, an application needs to decide if the user has deviated far enough and
        // what should happen next: For example, you can notify the user or simply try to
        // calculate a new route. When you calculate a new route, you can, for example,
        // take the current location as new start and keep the destination - another
        // option could be to calculate a new route back to the lastMapMatchedLocationOnRoute.
        // At least, make sure to not calculate a new route every time you get a RouteDeviation
        // event as the route calculation happens asynchronously and takes also some time to
        // complete.
        // The deviation event is sent any time an off-route location is detected: It may make
        // sense to await around 3 events before deciding on possible actions.
    }

上の例では、RouteDeviationdistanceInMeters に含まれる座標に基づいて距離が計算されます。ここでは、ルート上の予想位置と実際の位置との間の直線距離が示されます。遠すぎると思われる場合は、新しく計算したルートを VisualNavigator インスタンスに設定できます。これにより、それ以降のイベントはすべて、新しいルートに基づくようになります。

走行ガイダンス シナリオでは、lastLocationOnRoute および mapMatchedLocationnull になる可能性があります。routeDeviation.lastLocationOnRoutenull の場合、ユーザーはルートにまったく追随していませんでした。これは、開始地点が道路ネットワークから遠く離れているときに発生する可能性があります。通常、Navigator / VisualNavigator は道路に対して Location の更新のマッチングを試みます。ドライバーが遠すぎると、位置情報をマッチングできません。

イベントは非同期で配信されるため、キュー内の以前のイベントが引き続き古いルートに対して 1 回以上配信される可能性があります。これを防ぐには、必要に応じて新しいルートを設定した後に新しいリスナーを添付します。

Java版とKotlin版が利用可能なNavigationサンプルアプリでは、逸脱の検出方法を確認できます。

RouteDeviation イベントは、ドライバーが元のルートを離れることを検出するために使用できます。これは誤って行われる場合と、ルートの候補およびルート オプションについて以前に行った選択を無視してドライバーが運転中に目的地まで別のルートを利用することにしたなど意図的に行われる場合があります。

上に示したように、ドライバーの現在地からルート上の最後の既知の位置までの距離を検出できます。この距離に基づいて、新しいルート全体を計算するか、ユーザーを元のルートに誘導してルートの候補およびルート オプションの選択を維持するかがアプリケーションで決定される場合があります。

HERE SDK はルートを自動的に再計算せず、逸脱距離のみを通知します。したがって、ルートに戻る方法についてのロジックはアプリ側で実装する必要があります。

RouteDeviation イベントは、新しい位置情報の更新ごとに起動されます。不必要なイベントの処理を回避するため、数秒間待って、ドライバーがまだ逸脱しているかどうかを確認することをお勧めします。イベントが起動しなかった場合は、ドライバーがルートに戻ったということです。ルート計算は非同期で行われ、新しいルート計算をいつどのように開始するかはアプリによって決定されます。ただし、Navigator または VisualNavigator インスタンスへのナビゲーション中はいつでも新しいルートを設定でき、その先のイベントは新しく設定された Route インスタンスに基づいて更新されます。

ユーザーが道路外にいる場合があることも考える必要があります。新しいルートが設定された後もユーザーがまだ道路外におり、そのためユーザーがまだルートを追随できない場合があります。このような場合も新たに設定されたルートの逸脱イベントは受信され、routeDeviation.lastLocationOnRoute が null になります。ユーザーの現在地が変わっていない場合は、新しいルート計算を再度開始しないようにすることをお勧めします。

HERE SDK には、検出されたルート逸脱に対応する API がいくつかあります。

  1. RoutingEngine によって新規または更新された RouteOptions でルート全体を再計算し、新しいルートの候補を提供します。ユーザーの現在地を新しい出発地点として使用する場合は、最初の Waypoint の方位の向きも必ず指定します。
  2. returnToRoute() メソッドを使用して、最初に選択したルート候補に到達するための新しいルートを計算します。これは、オンライン RoutingEngine および OfflineRouteEngine で対応できます。OfflineRouteEngine で計算されたルートには、交通情報は含まれなくなりました。
  3. routingEngine.refreshRoute() で新しい出発地点を使用して古いルートを更新します。この出発地点は元のルート上にある必要があります。さらに、必要に応じてルート オプションを更新します。元のルートを特定するために RouteHandle が必要です。このオプションでは、逸脱した場所からルートに戻るパスは提供されないため、単独では逸脱のユースケースに適していません。
  4. 加えて HERE SDK では DynamicRoutingEngine が提供されており、現在の交通状況に基づいて最適化されたルートを定期的にリクエストできます。RouteHandle が必要となるため、オンラインで計算されたルートが必要です。これは、ユーザーがまだルートを追随する間に、より良いルートを見つけるためのエンジンです。したがって、入力として現在地が必要ですが、逸脱のユースケースには最適な選択ではない場合があります。

1 つ目と 3 つ目のオプションについては、ルート検索のセクションで説明しています。元のルートを更新する3つ目のオプションでは、逸脱した場所からルートに戻るパスは提供されません。そのため、以下では説明しません。ただし、アプリケーションにより、移動した部分をルートから削除して、ユーザーが自分で新しい出発地点に到達できるよう、この使用が選択される可能性があります。

アプリケーションは、逸脱した場所の距離や位置情報などのパラメーターに基づいて、ドライバーに提供するオプションを決定する必要があります。

ただし一般的には、逸脱が検出された場合は returnToRoute() を使用することをお勧めします。なぜなら、アプリでユーザーが選択できる複数のルートの候補が提供されている場合、始めに選択したルートの候補に誘導するのが最善策であるためです。

位置情報が更新されるたびに、HERE SDKはその位置情報を最も適した道路にマップマッチングしようとします。位置情報の更新を処理する前に、その精度を事前に評価して、非常に不正確と判断された位置情報のみ破棄します。その後、位置情報を道路ネットワークおよびアクティブなルートとマッチングします。逸脱イベントが発生する前に、内部的にさまざまなパラメーターが考慮されます。

逸脱後にルートに戻る

RoutingEngine または OfflineRoutingEngine によって、元のルートに戻るルートをオンラインまたはオフラインで計算します。最初に選択したルートを保持しつつ、ドライバーができるだけ早くルートに戻れるようにする場合は、returnToRoute() メソッドを使用します。

returnToRoute() は、ルートの逸脱に対応できる 1 つのオプションにすぎません。別のオプションについては、上の説明を参照してください。たとえば、ユーザーの目的地までの新しいルート全体を計算するといい場合もあります。

現在、returnToRoute() 機能ではエンジンと同じ移動モードがサポートされており、OfflineRoutingEngineRoutingEngine の両方を使用できます。RoutingEngine でこのメソッドを実行する場合、公共交通機関ルートのみがサポートされていません。RoutingEngine で利用可能なその他の移動モードはすべてサポートされています。

OfflineRoutingEnginereturnToRoute() メソッドには、キャッシュに保存されたマップ データまたはダウンロード済みのマップ データが必要です。ほとんどの場合、元のルートに戻るパスは、ドライバーがルートから逸脱する間にすでにキャッシュに保存されている可能性があります。ただし、大幅に逸脱している場合は、代わりに新しいルートを計算することを検討してください。

オンラインの RoutingEngine を使用する場合は、RouteHandle が含まれた元の Route が必要です。これがないと、ルート計算が NO_ROUTE_HANDLE エラーになります。OfflineRoutingEngine では、これは必要ありません。

ルート計算には、次のパラメーターが必要です。

  • 元の RouteNavigator または VisualNavigator から取得できます。
  • また、すでに移動したルートの一部を設定する必要があります。この情報は、RouteDeviation イベントによって提供されます。
  • 新しく開始する Waypoint。これは大抵の場合、マップマッチングされたドライバーの現在の位置となります。

新しい出発地点は、RouteDeviation イベントから取得できます。

// Get current geographic coordinates.
MapMatchedLocation currentMapMatchedLocation = routeDeviation.currentLocation.mapMatchedLocation;
GeoCoordinates currentGeoCoordinates = currentMapMatchedLocation == null ?
        routeDeviation.currentLocation.originalLocation.coordinates : currentMapMatchedLocation.coordinates;

// If too far away, consider to calculate a new route instead.
Waypoint newStartingPoint = new Waypoint(currentGeoCoordinates); // See RouteDeviation.
// Get current geographic coordinates.
val currentMapMatchedLocation = routeDeviation.currentLocation.mapMatchedLocation
val currentGeoCoordinates = currentMapMatchedLocation?.coordinates
    ?: routeDeviation.currentLocation.originalLocation.coordinates

// If too far away, consider to calculate a new route instead.
val newStartingPoint = Waypoint(currentGeoCoordinates); // See RouteDeviation.

オンラインの RoutingEngine では、まったく新しいルートが計算されることがあります。たとえば、ユーザーが以前に選択したルート候補よりも早く目的地に到達できる場合です。OfflineRoutingEngine では、ルートのまだ移動していない部分が優先的に再利用されます。

一般に、アルゴリズムは元のルートに戻る最速のルートを見つけようとしますが、目的地までの距離も考慮されます。新しいルートは、可能な場合は元のルート形状を維持しようとします。

まだ到着していない立ち寄る予定のストップオーバーはスキップされません。通過する経由地については、新しいルートで考慮されるとは限られません。

必要に応じて、ドライバーの進行方向を設定することにより、ルート計算を改善できます。

if (currentMapMatchedLocation != null && currentMapMatchedLocation.bearingInDegrees != null) {
    newStartingPoint.headingInDegrees = currentMapMatchedLocation.bearingInDegrees;
}
if (currentMapMatchedLocation != null && currentMapMatchedLocation.bearingInDegrees != null) {
    newStartingPoint.headingInDegrees = currentMapMatchedLocation.bearingInDegrees
}

最終的に、新しいルートを計算できます。

routingEngine.returnToRoute(
        originalRoute,
        newStartingPoint,
        routeDeviation.lastTraveledSectionIndex,
        routeDeviation.traveledDistanceOnLastSectionInMeters, new CalculateRouteCallback() {
    @Override
    public void onRouteCalculated(@Nullable RoutingError routingError, @Nullable List<Route> list) {
        if (routingError == null) {
            Route newRoute = list.get(0);
            // ...
        } else {
            // Handle error.
        }
    }
});
routingEngine.returnToRoute(
    lastCalculatedRoute!!,
    newStartingPoint,
    routeDeviation.lastTraveledSectionIndex,
    routeDeviation.traveledDistanceOnLastSectionInMeters,
    CalculateRouteCallback { routingError: RoutingError?, list: List<Route?>? ->
        // For simplicity, we use the same route handling.
        // The previous route will be still visible on the map for reference.
        handleRouteResults(routingError, list!!)
        // Instruct the navigator to follow the calculated route (which will be the new one if no error occurred).
        visualNavigator.route = lastCalculatedRoute
        // Reset flag and counter.
        isReturningToRoute = false
        deviationCounter = 0
        Log.d(TAG, "Rerouting: New route set.")
    })

CalculateRouteCallback が再利用されるため、ルートのリストが表示されます。ただし、リストには 1 つのルートしか含まれません。エラーの処理は、RoutingEngine と同じロジックに従います。

オンラインおよびオフラインの使用状況に関する一般的なガイドラインとして、returnToRoute() 機能は、すでに計算されている originalRoute のその先の部分を再利用しようとします。交通データは、オンラインの RoutingEngine で使用する場合にのみ更新され、考慮されます。

結果として生成される新しいルートでも、originalRoute にある同じ OptimizationMode が使用されます。

ただし最善の結果を提供するには、オンラインの RoutingEngine を使用し、交通状況に最適化されたルートを取得することをお勧めします。

上の推奨事項を使用した想定実装例を以下に示します。

private void handleRerouting(RouteDeviation routeDeviation,
                             int distanceInMeters,
                             GeoCoordinates currentGeoCoordinates,
                             MapMatchedLocation currentMapMatchedLocation) {
    // Counts the number of received deviation events. When the user is following a route, no deviation
    // event will occur.
    // It is recommended to await at least 3 deviation events before deciding on an action.
    deviationCounter ++;

    if (isReturningToRoute) {
        // Rerouting is ongoing.
        Log.d(TAG, "Rerouting is ongoing ...");
        return;
    }

    // When user has deviated more than distanceThresholdInMeters. Now we try to return to the original route.
    int distanceThresholdInMeters = 50;
    if (distanceInMeters > distanceThresholdInMeters && deviationCounter >= 3) {
        isReturningToRoute = true;

        // Use current location as new starting point for the route.
        Waypoint newStartingPoint = new Waypoint(currentGeoCoordinates);

        // Improve the route calculation by setting the heading direction.
        if (currentMapMatchedLocation.bearingInDegrees != null) {
            newStartingPoint.headingInDegrees = currentMapMatchedLocation.bearingInDegrees;
        }

        // In general, the return.to-route algorithm will try to find the fastest way back to the original route,
        // but it will also respect the distance to the destination. The new route will try to preserve the shape
        // of the original route if possible and it will use the same route options.
        // When the user can now reach the destination faster than with the previously chosen route, a completely new
        // route is calculated.
        Log.d(TAG, "Rerouting: Calculating a new route.");
        routingEngine.returnToRoute(lastCalculatedRoute,
                                    newStartingPoint,
                                    routeDeviation.lastTraveledSectionIndex,
                                    routeDeviation.traveledDistanceOnLastSectionInMeters,
                                    (routingError, list) -> {
            // For simplicity, we use the same route handling.
            // The previous route will be still visible on the map for reference.
            handleRouteResults(routingError, list);
            // Instruct the navigator to follow the calculated route (which will be the new one if no error occurred).
            visualNavigator.setRoute(lastCalculatedRoute);
            // Reset flag and counter.
            isReturningToRoute = false;
            deviationCounter = 0;
            Log.d(TAG, "Rerouting: New route set.");
        });
    }
}
private fun handleRerouting(
    routeDeviation: RouteDeviation,
    distanceInMeters: Int,
    currentGeoCoordinates: GeoCoordinates,
    currentMapMatchedLocation: MapMatchedLocation?
) {
    // Counts the number of received deviation events. When the user is following a route, no deviation
    // event will occur.
    // It is recommended to await at least 3 deviation events before deciding on an action.
    deviationCounter++

    if (isReturningToRoute) {
        // Rerouting is ongoing.
        Log.d(TAG, "Rerouting is ongoing ...")
        return
    }

    // When user has deviated more than distanceThresholdInMeters. Now we try to return to the original route.
    val distanceThresholdInMeters = 50
    if (distanceInMeters > distanceThresholdInMeters && deviationCounter >= 3) {
        isReturningToRoute = true

        // Use current location as new starting point for the route.
        val newStartingPoint = Waypoint(currentGeoCoordinates)

        // Improve the route calculation by setting the heading direction.
        if (currentMapMatchedLocation != null && currentMapMatchedLocation.bearingInDegrees != null) {
            newStartingPoint.headingInDegrees = currentMapMatchedLocation.bearingInDegrees
        }

        // In general, the return.to-route algorithm will try to find the fastest way back to the original route,
        // but it will also respect the distance to the destination. The new route will try to preserve the shape
        // of the original route if possible and it will use the same route options.
        // When the user can now reach the destination faster than with the previously chosen route, a completely new
        // route is calculated.
        Log.d(TAG, "Rerouting: Calculating a new route.")
        routingEngine.returnToRoute(
            lastCalculatedRoute!!,
            newStartingPoint,
            routeDeviation.lastTraveledSectionIndex,
            routeDeviation.traveledDistanceOnLastSectionInMeters,
            CalculateRouteCallback { routingError: RoutingError?, list: List<Route?>? ->
                // For simplicity, we use the same route handling.
                // The previous route will be still visible on the map for reference.
                handleRouteResults(routingError, list!!)
                // Instruct the navigator to follow the calculated route (which will be the new one if no error occurred).
                visualNavigator.route = lastCalculatedRoute
                // Reset flag and counter.
                isReturningToRoute = false
                deviationCounter = 0
                Log.d(TAG, "Rerouting: New route set.")
            })
    }
}

このコードでは、deviationCounter を使用して再ルート検索が不要に早く行われないようにします。さらに isReturningToRoute フラグを使用して、前の再ルート検索リクエストが完了する前に新しい再ルート検索リクエストが開始されるのを防ぎます。

距離しきい値は 50 m に設定しています。ユーザーがルートから逸脱して、距離がこのしきい値を超えた場合に再ルート検索が検討されます。

これらのチェックでは、新しい逸脱イベントを受信するたびにメソッドを呼び出して、RouteDeviation コールバックでの可能な再ルート検索に対応します。ユーザーがルートを追随している場合は、逸脱イベントは送信されません。また一方で、再ルート検索後に、現在地を新しい出発地点として新しいルートが設定されると、その時点でユーザーが再び逸脱していない限り、追加の逸脱イベントは発生しません。この場合、再ルート検索 プロセスが再度開始されます。


EN 日本語

HERE documentation

Find answers to your product and technical questions

Documentation

What's new

Videos

EN 日本語

HERE ドキュメント

製品や技術に関する質問の答えを見つけましょう。より多くの内容と最新の情報については、英語版をご覧ください。

ドキュメント

ダイナミックマップ

動的コンテンツ関連のAPIをアプリやサービスに活用して、ドライバーが安全・快適かつ予定どおりに目的地へ到着できるよう支援します。

地図とデータ

世界中を走行する多数のマッピング車両から得られる最新の位置情報データを活用し、精度の高い地図やカスタムレイヤーを構築できます。

最新情報

動画

(function () { const input = document.querySelector('input[data-typeahead]'); if (!input) return; // Prevent the form from submitting/navigating input.closest('form')?.addEventListener('submit', e => e.preventDefault()); input.addEventListener('input', function () { const q = this.value.trim().toLowerCase(); document.querySelectorAll('.nav-group-name').forEach(group => { let anyVisible = false; group.querySelectorAll('.nav-group-task').forEach(task => { const text = task.textContent.trim().toLowerCase(); const show = !q || text.includes(q); task.style.display = show ? '' : 'none'; if (show) anyVisible = true; }); // Hide the whole group header if nothing matches group.style.display = anyVisible || !q ? '' : 'none'; }); }); })(); (function () { function onTokenClick(event) { var link = event.target.closest('.sdk-for-ios .item .token'); if (!link) return; event.preventDefault(); console.log('token clicked', link.textContent.trim()); var item = link.closest('.item'); if (!item) return; var content = item.querySelector('.height-container'); if (!content) { console.log('no .height-container found for item', item); return; } var isHidden = window.getComputedStyle(content).display === 'none'; content.style.display = isHidden ? 'block' : 'none'; link.classList.toggle('token-open', isHidden); var href = link.getAttribute('href'); if (href) { if (history.pushState) history.pushState({}, '', href); else location.hash = href; } } function openHashTarget() { var hash = window.location.hash.slice(1); if (!hash) return; var anchor = document.querySelector('.sdk-for-ios a[name="' + hash + '"]'); if (!anchor) return; var item = anchor.closest('.item'); if (!item) return; var link = item.querySelector('.token'); var content = item.querySelector('.height-container'); if (!link || !content) return; content.style.display = 'block'; link.classList.add('token-open'); } function init() { console.log('HERE SDK accordion init'); openHashTarget(); } document.removeEventListener('click', onTokenClick); document.addEventListener('click', onTokenClick); if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); } window.addEventListener('hashchange', openHashTarget); window.addEventListener('pageLoad', init); })();