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

車両前方の地図情報

Electronic Horizonは、Navigateライセンスでのみ使用できます。

Electronic Horizonは地図をセンサーとして使用し、ドライバーの視界外にある地形情報を取り込むことにより、これから先の道路ネットワークを継続的に予測します。この機能は高度なナビゲーション、ドライバー支援、ADASに関連する機能において有用です。

これは本機能のベータリリースであるため、いくつかのバグや予期しない動作が発生する可能性があります。関連するAPIは、廃止のプロセスを経ずに、新しいリリースに変更される可能性があります。

Electronic Horizonの主なメリット:

  • 予測経路認識:車両が到達する前に、前方の道路セグメントに関する情報を早期に把握できます。最も優先される経路は確率に基づいて予測されます。
  • コンテキスト地図属性:道路標識、制限速度、その他の道路属性を前方情報に沿って照会します。
  • 動的な更新:前方情報の変化に応じて、必要なマップデータを自動的に読み込みます。
  • ルートおよびトラッキングのサポート:あらかじめ定義されたルート (マップマッチングモード) でも、ルートなし (トラッキングモード) でも動作します。

データの種類は次のとおりです。

  • Most Preferred Path (MPP、最も優先される経路) - 最も通行される可能性が高いと予測される道路です。
  • Alternative side paths (代替の側道) - MPPから分岐または逸脱する可能性のある道路です。
  • 制限速度 - 前方情報に沿った速度情報です。
  • 道路ジオメトリー - それぞれの前方道路セグメントを表すポリラインです。
  • 道路クラス - 道路の分類です。
  • 道路タイプ - 道路の特性です。
  • Toll points (料金所) - 今後通過する料金構造とその支払い情報です。
  • 信号機 - 今後出現する交通信号に関する情報です。
  • ...その他 - 設定されているデータ読み込みオプションに応じて異なります。SegmentDataクラスの概要を確認してください。

ElectronicHorizonEngineコンポーネントは、マップデータがキャッシュ、プリフェッチ、または端末にインストールされている場合、オフラインユースケースを完全にサポートします。ハイブリッドモードで動作するため、端末にデータが存在しない場合は自動的にオンラインから取得します。

HERE SDKはElectronic Horizon機能を提供しますが、現時点ではADASISv2プロトコルとADASISv3プロトコルの直接的なサポートは提供していません。

主な概念

コンセプト説明
車両前方の地図情報車両の前方にある道路ネットワークを継続的に更新するモデルです。
最も優先される経路 (MPP)過去の位置情報の更新またはアクティブなルートに基づいて、最も通行される可能性が高いルートセグメントです。
ElectronicHorizonDataLoader前方情報に沿ったセグメントに対して必要なマップデータを自動的に読み込むユーティリティです。
ElectronicHorizonUpdate現在の前方情報で新たに追加または変更された道路セグメントを表すデータ構造です。

VisualNavigatorと同様に、ElectronicHorizonEngineを位置情報で更新する必要があります。ただし、VisualNavigatorと異なり、この位置情報はマップマッチングされている必要があります。そのため、VisualNavigatorまたはNavigatorからのMapMatchedLocationを使用できます。

ElectronicHorizonEngineクラスは、MPPを通知するためにElectronicHorizonListenerを受け取ります。このリスナーは非同期でElectronicHorizonUpdateを配信しますが、初期状態では空のリストのみを含みます。

リスナーは呼び出しのたびにElectronicHorizonUpdateインスタンスを更新するため、クライアント側では1つのインスタンスのみを保持すれば十分です。任意の時点で、以下のリストを通じて車両前方の地図情報の現在の状態を表します。

  • electronicHorizonPathsElectronicHorizonOptionsと現在の車両位置に基づいて、車両前方の地図情報の経路ツリー全体を保持します。ElectronicHorizonDataLoadedStatusは、ツリーが完全に読み込まれたことを示します。次の更新により、新しい経路ツリーが生成され、部分的に読み込まれます。以降も同様に繰り返されます。
  • addedSegments:前回の更新以降に追加されたセグメントを保持します。これらの経路に基づき、必要なマップデータをデータローダーが要求します。
  • removedSegmentIds:前回の更新以降に削除されたセグメントIDを保持します。ElectronicHorizonEnginetrailingDistanceInMetersに基づいて車両の後方にあるセグメントを削除します。また、側道の分岐ポイントを通過した場合にもセグメントが削除されます。

新しいデータが読み込まれた際の通知を受け取るには、ElectronicHorizonDataLoaderStatusListenerを使用します。

典型的なシーケンスフローは以下のようになります。

  1. ElectronicHorizonListenerが最初のElectronicHorizonUpdateを提供します。
  2. その更新をElectronicHorizonDataLoaderを使って読み込みます。
  3. 更新に新規または変更されたデータが含まれている場合、ElectronicHorizonDataLoaderStatusListenerが通知します。
  4. ElectronicHorizonListenerが前回のデータに基づいて更新されたElectronicHorizonUpdateを提供します。
  5. 手順2を繰り返して、その更新を読み込みます。

なお、すべてのElectronicHorizonUpdateElectronicHorizonDataLoadedStatusが直ちに続くとは限りません。たとえば、連続して2回のElectronicHorizonUpdateイベントを受け取った後に、2回のElectronicHorizonDataLoadedStatusイベントを受信する可能性があります。

Electronic Horizon APIを統合する

Electronic Horizon APIを統合する前に、以下の前提条件を確認してください。

  1. プロジェクトにHERE SDKを統合していること。
  2. SDKNativeEngineを初期化するか、既存のインスタンスを再利用すること。
  3. 正確なホライズン更新のために、マップマッチングされた位置情報を提供するVisualNavigatorまたはNavigatorがあること。
  4. オプションとして、RoutePrefetcherを使用してオフライン用の地図データを取得するか、MapDownloaderを使用して移動に必要な地域データをインストールします。デバイスにマップデータが存在しない場合、Electronic Horizon APIは必要なマップデータをオンラインで取得します。

それでは、初期化から更新、データの取得まで、統合を始めましょう。

ElectronicHorizonEngineインスタンスを作成して設定する

ElectronicHorizonEngineクラスを使用して前方情報モデルを開きます。ルートを使用しないトラッキングモードの場合は、アクティブなRouteまたはnullを渡すかどうかを選択できます。

この例では、乗用車モードで車両前方の地図情報を有効化しますが、他の移動モードもサポートされています。

let lookAheadDistancesInMeters = [1000.0, 500.0, 250.0]
let trailingDistanceInMeters = 500.0
let electronicHorizonOptions = ElectronicHorizonOptions(
    lookAheadDistancesInMeters: lookAheadDistancesInMeters,
    trailingDistanceInMeters: trailingDistanceInMeters
)

let transportMode = TransportMode.car

do {
    electronicHorizonEngine = try ElectronicHorizonEngine(
        sdkEngine: ElectronicHorizonHandler.getSDKNativeEngine(),
        options: electronicHorizonOptions,
        transportMode: transportMode,
        route: route
    )
} catch let instantiationError {
    fatalError("ElectronicHorizonEngine is not initialized: \(instantiationError)")
}

ElectronicHorizonEngineでは、以下の内容をElectronicHorizonOptionsを使用して定義します。

  • メイン経路および側道に関するlookAheadDistancesInMeters:リストの最初のエントリーは最も優先される経路、2番目は第1レベルの側道、3番目は第2レベルの側道、というように定義されます。各エントリーでは、どの程度前方まで経路を提供するかを定義します。
  • 過去のセグメントが削除されるタイミングを定義するtrailingDistanceInMeters:HERE SDKは通過済みでtrailingDistanceInMetersを超える距離があるセグメントを削除します。

HERE SDKは、前方のセグメントを自動的にリスト管理します。前方情報が更新されるたびに、ElectronicHorizonListenerに通知されます。

マップマッチングした場所で更新する

VisualNavigatorまたはNavigatorからのMapMatchedLocationを使用して、ElectronicHorizonEngineを継続的に更新する必要があります。

electronicHorizonEngine.update(mapMatchedLocation: mapMatchedLocation)

更新のたびに、現在位置と進行方向に基づいて、優先される経路が再計算されます。

前方に追加された経路に関する通知を受け取る

データを読み込む前に、前方にある道路を知る必要があります。これには、ユーザーが移動するにつれて、車両前方の地図情報の更新を非同期で通知するデリゲートを作成します。そうすることにより、使用可能なセグメントIDとインデックスに関する情報が通知され、ElectronicHorizonDataLoaderによって後で実際のデータを要求できます。

private func createElectronicHorizonDelegate() -> ElectronicHorizonDelegate {
    class EHDelegate: ElectronicHorizonDelegate {
        weak var handler: ElectronicHorizonHandler?

        init(handler: ElectronicHorizonHandler) {
            self.handler = handler
        }

        func onElectronicHorizonUpdated(error: ElectronicHorizonError?, update: ElectronicHorizonUpdate?) {
            guard error == nil, let electronicHorizonUpdate = update else {
                print("(ElectronicHorizonHandler.LOG_TAG): ElectronicHorizonUpdate error: \(String(describing: error))")
                return
            }
            // Asynchronously start to load required data for the new segments.
            // Use the ElectronicHorizonDataLoaderStatusDelegate to get notified when new data is arriving.
            if electronicHorizonUpdate.electronicHorizon != nil {
                handler?.lastRequestedElectronicHorizon = electronicHorizonUpdate.electronicHorizon
            }
            if let segmentChanges = update.segmentChanges {
                handler?.electronicHorizonDataLoader.loadData(electronicHorizonUpdate: electronicHorizonUpdate)
            }
        }
    }
    return EHDelegate(handler: self)
}

このデリゲートをElectronicHorizionインスタンスに追加します。

データローダーを作成して設定する

ElectronicHorizonDelegateは読み込み可能な更新を提供します。これには、前方情報セグメントの詳細なマップデータを非同期に読み込むためのElectronicHorizonDataLoaderをインスタンス化する必要があります。SegmentDataLoaderOptionsを受け取り、含めるデータを定義できます。

// Many more options are available, see SegmentDataLoaderOptions in the API Reference.
var segmentDataLoaderOptions = SegmentDataLoaderOptions()
segmentDataLoaderOptions.loadRoadSigns = true
segmentDataLoaderOptions.loadSpeedLimits = true
segmentDataLoaderOptions.loadRoadAttributes = true

// The cache size defines how many road segments are cached locally. A larger cache size
// can reduce data usage, but requires more storage memory in the cache.
let segmentDataCacheSize = 10
do {
    electronicHorizonDataLoader = try ElectronicHorizonDataLoader(
        sdkEngine: ElectronicHorizonHandler.getSDKNativeEngine(),
        options: segmentDataLoaderOptions,
        segmentDataCacheSize: Int32(segmentDataCacheSize)
    )
} catch let instantiationError {
    fatalError("ElectronicHorizonDataLoader is not initialized: \(instantiationError)")
}

利便性のため、ElectronicHorizonDataLoaderElectronicHorizonEngineの最も優先される経路に基づいて必要なマップデータセグメントを継続的に読み込むSegmentDataLoaderをラップします。

第2ステップとして、ElectronicHorizonDataLoaderによって提供される新たに到着したマップデータセグメントの処理が必要になる場合があります。それには、ElectronicHorizonDataLoaderStatusListenerを作成します。HERE SDKはデータローダーのステータスが更新され新しいセグメントが読み込まれたときに、このデリゲートを呼び出します。

/// Handle newly arriving map data segments provided by the ElectronicHorizonDataLoader.
/// This delegate is called when the status of the data loader is updated and new segments have been loaded.
private func createElectronicHorizonDataLoaderStatusDelegate() -> ElectronicHorizonDataLoaderStatusDelegate {
    class EHStatusDelegate: ElectronicHorizonDataLoaderStatusDelegate {
        weak var handler: ElectronicHorizonHandler?

        init(handler: ElectronicHorizonHandler) {
            self.handler = handler
        }

        func onElectronicHorizonDataLoaderStatusUpdated(electronicHorizonDataLoaderStatuses statusMap: [Int32: ElectronicHorizonDataLoadedStatus]) {
            print("\(ElectronicHorizonHandler.LOG_TAG): ElectronicHorizonDataLoaderStatus updated.")
            // Access the loaded segments here.
        }
    }

    return EHStatusDelegate(handler: self)
}

このデリゲートをElectronicHorizonDataLoaderインスタンスに追加します。

セグメントデータにアクセスする

onElectronicHorizonDataLoaderStatusUpdated(...)を通じてElectronicHorizonDataLoaderStatusListenerがセグメントが完全に読み込まれたことを通知したら、個々のセグメントの属性を照会できます。

[Int32: ElectronicHorizonDataLoadedStatus]型のstatusMapパラメーターには以下の情報が含まれます。

  • 整数のキーは、最も優先される経路のレベル (0) および側道 (1、2、...) を表します。
  • ステータスには、そのセグメントが完全に読み込まれて使用可能かどうかの情報が含まれます。たとえばElectronicHorizonDataLoadedStatus.FULLY_LOADEDをチェックして確認できます。

これで、以前に要求された車両前方の地図情報の更新の一部であったセグメントにアクセスできます。アプリはelectronicHorizonDataLoader.loadData(...)の呼び出し内で、これらのセグメントを読み込むように要求しました。データローダーはelectronicHorizonDataLoader.getSegment(directedOCMSegmentId.id)を通じて実際のデータにアクセスするメソッドを提供します。

内部的には、データローダーはアプリが要求したセグメントを追跡し、提供されたElectronicHorizonUpdateインスタンスを継続的に更新します。

以下の例では、MPPが完全に読み込まれるのを待ってから、ツリー全体を繰り返し処理します。

for (level, status) in statusMap {
    // The integer key represents the level of the most preferred path (0) and side paths (1, 2, ...).
    // This example shows only how to look at the fully loaded segments of the most preferred path (level 0).
    if level == 0 && status == .fullyLoaded {
        // Now, level 0 segments have been fully loaded and you can access their data.
        // The electronicHorizonPaths list contains segments from all levels,
        // so you need to filter for level 0 below.
        for electronicHorizonPath in lastUpdate.electronicHorizonPaths {
            let electronicHorizonPathSegments = electronicHorizonPath.segments
            for segment in electronicHorizonPathSegments {
                // For any segment you can check the parentPathIndex to determine
                // if it is part of the most preferred path (MPP) or a side path.
                if segment.parentPathIndex != 0 {
                    // Skip side path segments as we only want to log MPP segment data in this example.
                    // And we only want to log fully loaded segments.
                    continue
                }

                guard let directedOCMSegmentId = segment.segmentId.ocmSegmentId else {
                    continue
                }

                // Retrieving segment data from the loader is executed synchronous. However, since the data has been
                // already loaded, this is a fast operation.
                let result = handler.electronicHorizonDataLoader.getSegment(segmentId: directedOCMSegmentId.id)
                if result.errorCode == nil, let segmentData = result.segmentData {
                    // Access the data that was requested to be loaded in SegmentDataLoaderOptions.
                    // For this example, we just log road signs.
                    guard let roadSigns = segmentData.roadSigns, !roadSigns.isEmpty else {
                        continue
                    }
                    for roadSign in roadSigns {
                        let roadSignCoordinates = handler.getGeoCoordinatesFromOffsetInMeters(
                            geoPolyline: segmentData.polyline,
                            offsetInMeters: Double(roadSign.offsetInMeters)
                        )
                        print("\(ElectronicHorizonHandler.LOG_TAG): RoadSign: type = \(roadSign.roadSignType.rawValue), offsetInMeters = \(roadSign.offsetInMeters), lat/lon: \(roadSignCoordinates.latitude)/\(roadSignCoordinates.longitude), segmentId = \(directedOCMSegmentId.id.localId)")
                    }
                }
            }
        }
    }
}

セグメントから地理座標を取得するには、提供されたメートル単位のオフセットと、以下のヘルパーを使用できます。

/// Convert an offset in meters along a GeoPolyline to GeoCoordinates using the HERE SDK's coordinatesAtOffsetInMeters.
private func getGeoCoordinatesFromOffsetInMeters(geoPolyline: GeoPolyline, offsetInMeters: Double) -> heresdk.GeoCoordinates {
    return geoPolyline.coordinatesAt(offsetInMeters: offsetInMeters,
                                     direction: .fromBeginning)
}

MPPの場合、segment.parentPathIndexは0になります。必要に応じて、ツリーのさらに下位へ反復処理を行い、側道に関する情報を取得することもできます。

停止してクリーンアップする

ナビゲーションが終了するかトラッキングモードが停止したときは、リスナーを削除しリソースを解放します。

electronicHorizonEngine.removeElectronicHorizonListener(listener);
electronicHorizonDataLoader.removeElectronicHorizonDataLoaderStatusListener(statusListener);

ベストプラクティス

  • ユースケースに合った先読み距離や追跡距離を使用します (例:高速道路対都市部)。
  • 側道データを積極的に必要としない限り、レベル0 (最も優先される経路) セグメントのみを処理します。
  • アプリがオフラインまたは低接続環境で実行される場合は、地図データをプリフェッチします。
  • メモリとパフォーマンスのバランスをとるために、データローダーには合理的なキャッシュサイズを選択します。
  • 不要になったらリスナーを削除しリソースを解放して、メモリリークや不要なバックグラウンド処理を回避します。

サンプルアプリを試す

GitHubの「Navigation」サンプルアプリでは、ElectronicHorizonHandlerクラスが以下の方法を示しています。

  • Electronic Horizonを初期化して開始する。
  • ナビゲーション中にマップマッチングした場所を使用して更新する。
  • 車両がルートに沿って移動するにつれて、道路レベルのデータ (例:道路標識) を取得しログに記録する。

完全な実装については、
ElectronicHorizonHandler.swiftを参照してください。


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