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

ナビゲーションアプリを作成する

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

ガイダンスを開始するには、2つの方法があります。ヘッドレス Navigator を使用するか、VisualNavigator を使用します。どちらの場合もインターフェースは同じですが、Navigator では VisualNavigator のサブセットが提供されるのに対して、VisualNavigator では上部にビジュアル レンダリング アシスタンスが表示され、個別の Location の更新間のスムーズな補間などの機能が提供されます。

以下では、VisualNavigator を使用します。

ナビゲーション アプリの基本的な原理は次のとおりです。

  1. Route を作成します。トラッキング モードを開始しない限り、追随するルートがないとルート案内を開始できません。
  2. VisualNavigator インスタンスを作成してレンダリングを開始します (独自のガイダンス ビューをレンダリングする場合は Navigator インスタンスを作成します)。
  3. RouteVisualNavigator に設定します。トラッキング モードを開始する場合を除きます。
  4. VisualNavigator に位置情報の更新がフィードされます。位置情報データがないと、ルート上のルート進行状況を検出できません。また、道路標識などの今後の道路イベントのイベントは、ユーザーの位置を把握しないと利用できません。位置をシミュレートすることも、実際の位置情報の更新をフィードすることもできます。

GitHub の NavigationQuickStart アプリは、ナビゲーション アプリの基本原則を示しています。

ナビゲーション アプリの基本スケルトンは次のようになります。

private func startGuidance(route: Route) {
    do {
        // Without a route set, this starts tracking mode.
        try visualNavigator = VisualNavigator()
    } catch let engineInstantiationError {
        fatalError("Failed to initialize VisualNavigator. Cause: \(engineInstantiationError)")
    }

    // This enables a navigation view including a rendered navigation arrow.
    visualNavigator!.startRendering(mapView: mapView)

    // Hook in one of the many delegates. Here we set up a delegate to get instructions on the maneuvers to take while driving.
    // For more details, please check the "Navigation" example app and the Developer Guide.
    visualNavigator!.eventTextDelegate = self

    // Set a route to follow. This leaves tracking mode.
    visualNavigator!.route = route

    // VisualNavigator acts as LocationDelegate to receive location updates directly from a location provider.
    // Any progress along the route is a result of getting a new location fed into the VisualNavigator.
    setupLocationSource(locationDelegate: visualNavigator!, route: route)
}

// Conform to EventTextDelegate.
func onEventTextUpdated(_ eventText: heresdk.EventText) {
    print("Maneuver text: \(eventText.text)")
}

開始するには、LocationSimulator を使用して、シミュレーションされたソースを選択します。このクラスは、ルートの道路形状に基づいて位置イベントを提供します。

private func setupLocationSource(locationDelegate: LocationDelegate, route: Route) {
    do {
        // Provides fake GPS signals based on the route geometry.
        try locationSimulator = LocationSimulator(route: route,
                                                  options: LocationSimulatorOptions())
    } catch let instantiationError {
        fatalError("Failed to initialize LocationSimulator. Cause: \(instantiationError)")
    }

    locationSimulator!.delegate = locationDelegate
    locationSimulator!.start()
}

上記のコード スニペットは、Route から取得された、シミュレートされた Location イベントを使用してシミュレートされたルート案内を開始するために必要なすべてのコードを示しています。これは VisualNavigator インスタンスを使用しているため、stopRendering() が呼び出されるまで、HERE SDK がレンダリング部分を引き継ぎます。

これで、必要に応じてイベントのリスナーまたは警告をVisualNavigatorインスタンスに追加できるようになりました。リスナーの添付に特別なことは何もありません。基本的には、関連するナビゲーションセクションに示されているコードスニペットを使用できます。

今後は、運転操作などのイベントデータの可視化に役立つ、より使いやすいUI構成要素を提供する予定です。

以下では、柔軟な位置情報プロバイダーを構築するために推奨される手順を詳しく説明します。また、開発中にテストするためのシミュレーター クラスを作成する方法についても説明し、ルート再生をカスタマイズするためのさまざまなオプションを提示します。

位置情報プロバイダーを実装する

位置情報プロバイダーは Location インスタンスを VisualNavigator に提供する必要があります。位置情報データは任意のソースからフィードできます。ここでは、デバイスからのネイティブな位置情報データとテスト運転用のシミュレーション位置情報データを切り替えることができる実装を使用します。

上記のように、VisualNavigatorLocationDelegateプロトコルに準拠しているため、onLocationUpdated(location:)を呼び出すクラスのデリゲートとして使用できます。

位置情報データのソースとして、「ポジショニング」セクションで示したコードに基づいたHEREPositioningProviderを使用します。

ナビゲーションでは LocationEngine の起動時に LocationAccuracy.navigation を使用することをお勧めします。これにより、ターン・バイ・ターンナビ中に最適な結果が保証されます。

イベントを配信するには、herePositioningProvider を起動する必要があります。

herePositioningProvider.startLocating(locationDelegate: visualNavigator,
                                      accuracy: .navigation)

必要とされる HERE SDK Location タイプには、現在の地理座標および VisualNavigator によって消費されるその他の情報とともに方位と速度の情報が含まれています。提供されたデータが正確で完全であるほど、全体的なナビゲーション エクスペリエンスはより正確になります。

Location オブジェクトから取得される bearing 値によって移動の方向が決まり、その方向に合わせて回転する LocationIndicator アセットによって移動の方向が示されます。ユーザーが移動していない場合は、新しい方位の値が設定されるまで最後の回転が保持されます。Location データのソースにより、この値の正確性には差があります。

内部的には、 Locationtimestamp は、ユーザーがトンネルを走行しているのか、あるいは単に信号が失われているかなどを評価するために使用されます。

位置情報プロバイダーのリファレンス実装は GitHub で確認できます。

位置情報シミュレーターを設定する

開発中は、テストのためにルート上で予想される進行状況を再生するのに役立つ可能性があります。LocationSimulator は元のルートの座標から取得された連続した位置信号を提供します。

以下では、LocationSimulator を代替プロバイダーとして統合し、実際の位置情報の更新とシミュレートされた位置を切り替えられるようにします。

import heresdk

// A class that provides simulated location updates along a given route.
// The frequency of the provided updates can be set via LocationSimulatorOptions.
class HEREPositioningSimulator {

    private var locationSimulator: LocationSimulator?

    func startLocating(locationDelegate: LocationDelegate, route: Route) {
        if let locationSimulator = locationSimulator {
            locationSimulator.stop()
        }

        locationSimulator = createLocationSimulator(locationDelegate: locationDelegate, route: route)
        locationSimulator!.start()
    }

    func stopLocating() {
        if locationSimulator != nil {
            locationSimulator!.stop()
            locationSimulator = nil
        }
    }

    // Provides fake GPS signals based on the route geometry.
    private func createLocationSimulator(locationDelegate: LocationDelegate,
                                         route: Route) -> LocationSimulator {
        let notificationIntervalInSeconds: TimeInterval = 0.5
        let locationSimulatorOptions = LocationSimulatorOptions(speedFactor: 2,
                                                                notificationInterval: notificationIntervalInSeconds)
        let locationSimulator: LocationSimulator

        do {
            try locationSimulator = LocationSimulator(route: route,
                                                      options: locationSimulatorOptions)
        } catch let instantiationError {
            fatalError("Failed to initialize LocationSimulator. Cause: \(instantiationError)")
        }

        locationSimulator.delegate = locationDelegate
        locationSimulator.start()

        return locationSimulator
    }
}

さらに、LocationSimulatorOptions を設定することで、現在のシミュレートされた位置がどのくらい速く移動するかを指定できます。デフォルトでは notificationInterval が 1、speedFactor が 1.0 で、これは、交通状況に関連する制約を考慮せずにユーザーが各ルート セグメントを通常運転または歩行する平均速度に相当します。デフォルトの速度は、道路形状、道路条件、その他の統計データによって異なりますが、現在の制限速度を超えることはありません。値が 1.0 を超えると、比例して速度が増加します。指定の時間間隔に十分な座標がルートに含まれていない場合、追加の位置イベントが VisualNavigator によって補間されます。

LocationSimulator によって発信される位置情報は補間されず、ソースに基づいて提供されます。Route の場合、ルート ジオメトリーの座標が使用されます (互いに非常に近いもの)。GPXTrack の場合、座標は GPX データに基づいて発信されます。たとえば、2 つの座標の間に数百メートルある場合、時間設定に基づいて 2 つの座標のみが発信されます。ただし、VisualNavigator にフィードされると、レンダリングされたマップ アニメーションは VisualNavigator によって補間されます。

連続する Location の更新間の距離が 100 m を超える場合、VisualNavigator はアニメーションをスキップします。speedFactor が増加すると、位置情報の更新間の距離も変わります。通知の間隔がそれに応じて調整されない場合は、たとえば速度係数を 8 に変更するなら、Location の更新間の距離の一貫性を保つために、通知間隔も 125 ms (1000 ms / 8) に変更します。notificationIntervalspeedFactor は反比例します。したがって、speedFactor が 3 であれば、推奨される notificationInterval は 330 ms になります。

以下のコードは enableRoutePlayback(route:)enableDevicePositioning() を呼び出すことで、シミュレーションされた位置情報と実際の位置情報を途切れることなく切り替える方法を示しています。

// Provides location updates based on the given route.
func enableRoutePlayback(route: Route) {
    herePositioningProvider.stopLocating()
    herePositioningSimulator.startLocating(locationDelegate: visualNavigator, route: route)
}

// Provides location updates based on the device's GPS sensor.
func enableDevicePositioning() {
    herePositioningSimulator.stopLocating()
    herePositioningProvider.startLocating(locationDelegate: visualNavigator,
                                          accuracy: .navigation)
}

新しいシミュレーションを開始する前に、進行中のシミュレーションまたは実際の位置情報ソースを必ず停止する必要があります。

上記のコードは GitHub の 「Navigation」のサンプル アプリに含まれています。


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); })();