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

代替オプション

オフラインマップはNavigateライセンスでのみ使用できます。

マップデータをRegionsとして永続ストレージにインストールする以外に、MapDownloaderを使うことなく、データをキャッシュにプリフェッチすることもできます。

マップデータをプリフェッチする

HERE SDK は、マップ データのルート プリフェッチのサポートを提供します。これにより、ユーザーエクスペリエンスが向上し、たとえば、ターン・バイ・ターンナビ中の一時的なネットワーク損失を適切に処理できます。

移動する地域のオフラインマップがすでにダウンロードされている場合、これは必要ありません。その場合はすべてのマップデータがすでに存在するため、ネットワーク接続は必要ありません。専用の OfflineRoutingEngine などとは異なり、Navigator または VisualNavigator では、キャッシュに保存されたデータまたはオフライン マップ データにフォールバックする必要があるかどうかが自動的に決定されます。一般に、マップ ビューを表示せずにヘッドレスでナビゲーションを実行する場合でも、マップ データは必要です。その理由は、マップ マッチングのためにナビゲーション中にマップ データにアクセスしたり、制限速度などの特定の道路属性について通知したりする必要があるためです。このデータは、デバイス上の利用可能なデータから取得されます。デバイス上にない場合は、ナビゲーション中にダウンロードする必要があります。そのため、先の道路を見越してデータをより多くプリフェッチすると役立ちます。プリフェッチしておかないと、一時的なネットワーク接続の損失を適切に処理できなくなる可能性があります。

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

RoutePrefetcher コンストラクタには、唯一のパラメーターとして SDKNativeEngine インスタンスが必要です。HERE SDK の初期化が完了した後、SDKNativeEngine.sharedInstance から取得できます。

RoutePrefetcher では、マップ データを事前にダウンロードすることができます。マップ データはマップ キャッシュに読み込まれます。マップキャッシュには独自のサイズ制限があり、すでにデータが含まれている可能性があります。RoutePrefetcherでは、新しいマップデータを保存するために過去にキャッシュに保存されたデータの削除が必要になる場合があります。

  • 移動を開始する前に、現在地のマップデータをプリフェッチすることをお勧めします。これにより、最初のガイダンスセグメントに必要な情報がすべてキャッシュされます。例:

    let geoCircle = GeoCircle(center: currentGeoCoordinates, radiusInMeters: 10000.0)
    polygonPrefetcher.prefetch(
        geoPolygon: GeoPolygon(geoCircle: geoCircle),
        callback: { MyPrefetchStatusListener(messageDelegate: self.messageDelegate) }()
    )
    

    この呼び出しは、指定されたエリアのマップデータをマップキャッシュに読み込み、ユーザーがルートの追随を開始したときに利用可能な十分なデータがあることを確認します (ルートが現在地から始まる場合)。プリフェッチは非同期で実行され、空き実行スレッドのみを使用するため、アプリ全体のパフォーマンスへの影響は最小限に抑えられます。

  • ナビゲーションが開始した後に、routePrefetcher.prefetchAroundRouteOnIntervals​(navigator)を1回呼び出すことを検討してください。提供された Navigator インスタンスに現在設定されているルート上の軌道のマップ データをプリフェッチします。ルートが設定されていない場合、データはプリフェッチされません。ルートの軌道のデフォルトの長さは 10 km、幅は 5 km です。マップ データは、離散的な間隔でのみプリフェッチされます。プリフェッチは、現在の軌道の終わりに到達する 1 km 手前で開始します。プリフェッチは、RouteProgress イベントによって示される、現在のマップマッチングした場所に基づいて行われます。最初のプリフェッチは、ルート上を 9 km 移動した後に開始されます。新しいルートを navigator に設定しても、このメソッドを再度呼び出す必要はありません。ただし、2 回以上呼び出したとしても悪い影響はありません。

「Navigation」のサンプル アプリで RoutePrefetcher の使用方法の例を確認できます。

ルートの開始時に RoutePrefetcher が正常に使用され、その後接続が失われた場合、キャッシュに保存されたデータはマップ キャッシュが削除されるまで、その先の電源サイクルでも保持されます。マップ キャッシュの削除ポリシーの詳細については、こちらを参照してください。

  • 利便性のためナビゲーションを開始する前に両方のメソッドを同時に呼び出すこともできます。ただしその代わりに、その後すぐに移動が開始される場合は、必要なすべてのデータをプリフェッチする十分な時間がない可能性があります。
  • prefetchAroundRouteOnIntervals() はガイダンス中のネットワーク トラフィックを絶えず増加させます。

もちろん、プリフェッチされたデータがなくてもガイダンスは可能ですが、エクスペリエンスの十分に最適化されない可能性があります。

どちらの呼び出しも、キャッシュに保存されたマップ データを利用する一時的なオフライン ユースケースの最適化に役立ちます。prefetchAroundLocationWithRadius() はナビゲーションのユースケース以外にも使用できますが、prefetchAroundRouteOnIntervals() には進行中のナビゲーション シナリオが必要です。

別の方法として、ルート全体のマップ データを事前にプリフェッチすることもできます。RoutePrefetcher.prefetchGeoCorridor() を使用して、ルート形状から作成された GeoCorridor のタイル データをプリフェッチします。これにはルートの長さ、軌道の幅、ネットワークによって少し時間がかかることがあるため、進捗状況が PrefetchStatusListener.onProgress() でレポートされます。操作が完了すると、PrefetchStatusListener.onComplete() イベントが送信されます。

ルートですでにダウンロードされた Region データが渡される場合、軌道のこれらの部分は再利用され、再度ダウンロードされません。
同様に、プリフェッチでは、マップ キャッシュにすでにあるデータが 2 回ダウンロードされることはありません。一般に、プリフェッチされたすべてのデータがデバイスに永続的に保存されるわけではなく、後で新しいデータが読み込まれると、削除される可能性があります。また、空き容量は、ユーザーが設定できるマップ キャッシュ サイズによって異なります。十分な空き容量がない場合は、MapLoaderError が発生します。マップ キャッシュの詳細については、こちらを参照してください。

マップ データをプリフェッチするもう 1 つの方法は、PolygonPrefetcher を使用して実装できます。この方法では、実在の Route から独立したデータを取得できます。代わりに、データをダウンロードする場所を指定するために GeoPolygon のみが必要です。このデータはマップ キャッシュに一時的にのみ保存されます。その点は、RoutePrefetcher と同様です。PolygonPrefetcher では、データをダウンロードする前に、ダウンロードの予想される MapDataSize を見積もることもできます。

エリアをダウンロードする

事前定義されたRegionをダウンロードする代わりに、カスタムエリアを指定してダウンロードすることもできます。このエリアはポリゴンとして設定することも、たとえば、以下に示すようにGeoBoxとして設定することもできます。

HERE SDKは、そのエリアが以前に別のエリアやRegionの一部としてダウンロードされていた場合でも、マップデータを一度保存するだけでストレージを最適化します。

Regionデータのダウンロードに使用されるDownloadRegionsStatusListenerは、エリアのダウンロードにも使用されます。インストールの進行状況についても同様の方法で更新を提供します。エリアのダウンロードが完了すると、リストにはそのエリアを特定する一意のRegionIdが1つだけ含まれます。このIDを人間が読める名前で保存することをお勧めします。そうすることで、将来的にmapDownloader.deleteRegions(...)を呼び出してダウンロード済みのエリアを削除するのが容易になります。ダウンロードしたエリアのIDには、InstalledRegionsを通じてアクセスできます。

mapDownloader.downloadArea(...)を並列で呼び出して、複数のエリアを同時にダウンロードできます。

ビューポートに現在表示されているエリアを永続的にインストールする方法の例を次に示します。

func onDownloadAreaClicked() {
    let downloadAreaStatusListenerImpl = DownloadRegionsStatusListenerImpl()
    downloadAreaStatusListenerImpl.showDialog(title: "Note",
                                              message: "Downloading the area that is currently visible in the viewport.")

    let polygonArea = GeoPolygon(geoBox: getMapViewGeoBox())
    _ = mapDownloader?.downloadArea(area: polygonArea,
                                    statusListener: downloadAreaStatusListenerImpl)
}

private class DownloadRegionsStatusListenerImpl: DownloadRegionsStatusListener {
    func onDownloadRegionsComplete(error: MapLoaderError?, regions: [RegionId]?) {
        if let mapLoaderError = error {
            showDialog(title: "Error",
                       message: "Download area completion error: \(mapLoaderError.localizedDescription)")
            return
        }

        if let regionId = regions?.first {
            let message = "Download area status. Completed 100%! ID: \(regionId.id)"
            print(message)
        }
    }

    func onProgress(region: RegionId, percentage: Int32) {
        // Note that this ID is uniquely created and can be used to delete the area in the future.
        let message = "Download of area. ID: \(region.id). Progress: \(percentage)%."
        print(message)
    }

    func onPause(error: MapLoaderError?) {
        if let mapLoaderError = error {
            showDialog(title: "Error",
                       message: "Download area onPause error. The task tried too often to retry the download: \(mapLoaderError.localizedDescription)")
        } else {
            showDialog(title: "Info",
                       message: "The area download was paused by the user calling mapDownloaderTask.pause().")
        }
    }

    func onResume() {
        showDialog(title: "Info", message: "A previously paused area download has been resumed.")
    }

    func showDialog(title: String, message: String) {
        // ...
    }
}

この例では、GeoBoxを使用してダウンロードするリージョンを定義します。上記のコードには、getMapViewGeoBox()のコードが含まれており、GitHubにある付属の「OfflineMaps」サンプルアプリで確認できます。

インストールされているリージョンのリストを取得する

MapDownloaderを使用して、InstalledRegion要素のリストを取得し、現在デバイスにインストールされているリージョンを確認できます。インストールステータスを確認することもできます。region.sizeOnDiskInBytesを使用し、次に示すように、ストレージの合計使用量を計算できます。

// Get the list of all downloaded regions.
func getInstalledRegionsList() -> [InstalledRegion] {
    var installedRegionList: [InstalledRegion] = []
    if let downloader = mapDownloader {
        do {
            if let regions = try? downloader.getInstalledRegions() {
                installedRegionList = regions
            } else {
                print("Error: Installed regions not found.")
            }
        }
    } else {
        print("Error: MapDownloader is nil.")
    }

    return installedRegionList
}

// Log all the regions downloaded and total storage usage of downloaded map areas.
func logInstalledRegions() {
    let installedRegionList = getInstalledRegionsList()
    for region in installedRegionList {
        print("Downloaded region id: \(region.regionId)")
        print("sizeOnDiskInBytes: \(region.sizeOnDiskInBytes)")
        print("InstalledRegionStatus: \(region.status)")
    }

    let occupiedStorageSize = installedRegionList.reduce(0) { $0 + $1.sizeOnDiskInBytes }
    print("Storage usage: \(occupiedStorageSize) Bytes")
}

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