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

マップ データをインストールする

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

以下では、MapDownloaderを使用してオフラインマップデータをアプリケーションに統合する方法を説明します。

  1. Region 地物のリストをダウンロードします。オプションで、このリストを現地の地域名を使用してローカライズできます。このリストを取得したら、ダウンロードする RegionId を選択し、それをダウンロード リクエストとして MapDownloader に渡すことができます。
  2. MapDownloader を使用して、単一の Region または地域のリストをダウンロードします。複数の地域を並行してダウンロードすることもできます。DownloadRegionsStatusListenerを設定して、ダウンロードの進行状況をユーザーに表示します。RAMの負荷を下げるために、このようなデバイスでは順番にダウンロードすることを検討してください。
  3. MapUpdaterを使用して、ダウンロード済みの地域とマップキャッシュを新しいマップバージョンに更新します。

ダウンロードが完了すると、地図を使用できるようになります。デバイスがオフラインの場合、カメラのターゲットがその地域に向けられると、ダウンロードした地域が自動的に表示されます。

ダウンロードが失敗した場合でも、HERE SDK は完全に動作可能な状態のままです。進行状況が 100% に達し、最終的に操作が完了したことを示すステータスが表示されるまで、再度ダウンロードをお試しください。問題がより深刻な場合は、修復を試してください。詳細については、以下の修復のセクションを参照してください。

アプリがバックグラウンドで実行されている間にマップデータのダウンロードや更新を続けることは推奨されません。ベストプラクティスとして、ユーザーがアプリをフォアグラウンドで実行し続けられない場合には、継続中のダウンロードを pause() または cancel() するように通知します。また、アプリが一時停止後に再開される際に resume() オプションを提供することを検討してください。

デバイス (特に GPU と CPU) をアクティブにし続け、アプリがアイドル状態にならないようにするために、プラットフォーム固有のコードを記述することを検討します。または、アプリの機能を維持するために画面をオンのままにしておくようユーザーに通知します。

地域、国、大陸全体のマップ データは数百メガバイトになる場合があるため、利用可能な帯域幅などの要因によってはダウンロードにかなりの時間がかかる可能性があります。さらに、接続がタイムアウトして回復できない場合、ダウンロードが失敗することがあります。ユーザーの操作性を向上するために、ユーザーが進行中の操作をキャンセルできるようにし、地図のダウンロードが成功するまで進行状況を監視できるようにすることをお勧めします。

これらの機能について概要を簡単に確認するには、OfflineMapsExample クラスを参照してください。これには以下に示すすべてのコードスニペットが含まれており、GitHubにある「OfflineMap」のサンプルアプリの一部です。

MapDownloader を使用する

SDKNativeEngine ごとに MapDownloader を 1 回使用できます。

guard let sdkNativeEngine = SDKNativeEngine.sharedInstance else {
    fatalError("SDKNativeEngine not initialized.")
}

// Create MapDownloader in background to not block the UI thread.
MapDownloader.fromEngineAsync(sdkNativeEngine, { mapDownloader in
    self.mapDownloader = mapDownloader
})

通常、アプリを起動すると SDKNativeEngine が自動的に初期化され、MapView が表示されます。したがって、実行時にそのインスタンスにアクセスし、そこから MapDownloader を取得できます。

すべてのインスタンスの準備が整うまで待機するために、ディスパッチ グループなど専用のローダー メソッドまたはクラスを使用することを検討してください。この処理中に読み込みインジケーターを表示すると、ユーザー エクスペリエンスが向上します。このアプローチにより、nil チェックを行わずにクラス内でインスタンスを自信を持って使用できるようになります。通常、MapDownloaderMapUpdater の作成 (以下参照) はごくわずかな時間で完了します。

デフォルトでは、ダウンロードしたマップ データはデフォルトの場所に保存されます。

// Note that the default storage path can be adapted when creating a new SDKNativeEngine.
let storagePath = sdkNativeEngine.options.cachePath
showMessage("This example allows to download the region Switzerland. StoragePath: \(storagePath).")

コメントに記載のとおり、必要に応じて保存場所を変更できますが、その場合は、「主な概念」セクションにあるように、新しい SDKNativeEngine インスタンスを作成し、資格情報とともに新しいキャッシュ パスを SDKOptions の一部として設定する必要があります。ストレージ パスは、資格情報キーに対して一意であることに注意してください。

persistentMapStoragePathプロパティは、永続マップデータが格納される場所を定義します。デフォルトでは、これは空の文字列を返し、SDKは内部アプリストレージを使用します。設定されている場合は、読み取り/write権限のある有効なディレクトリパスにします。永続マップの格納場所に読み取り専用権限がある場合は、dataPathを設定する必要があります。永続マップストレージパスは、アプリ固有のディレクトリに配置します。ドキュメントなどの共有ディレクトリを使用することは、HERE SDKファイルが他のアプリに公開されるため、推奨されません。

// This is the default path for storing downloaded regions.
// The application must have read/write access to this path if updating it.
let persistentMapStoragePath = sdkNativeEngine.options.persistentMapStoragePath
print("Persistent map storage path: \(persistentMapStoragePath).")

地域のリストをダウンロードする

ダウンロード可能な各 Region は一意の RegionId によって識別されます。どの地域が利用でき、どの RegionID がどの Region に属しているかを知るには、利用できるすべてのオフライン マップのリストをダウンロードする必要があります。このリストには全世界の地域が含まれています。

Region は複数の子を含むことができ、それぞれの子は親 Region のサブセットとなります。親をダウンロードすると、子地域が自動的に含まれます。地域のより狭い部分のみに興味がある場合は、子地域を横断できます。通常、トップレベルの地域は、国を子として持つ大陸を表します。分かりやすくするために、以下ではダウンロード可能な国のみを探し、子とその子の子 (など) は無視します。

以下のコードはダウンロードできる地域のリストをダウンロードし、後で使用できるように Region 要素をリストに保存します。

func onDownloadListClicked() {
    // Download a list of Region items that will tell us what map regions are available for later download.
    _ = mapDownloader.getDownloadableRegions(languageCode: LanguageCode.deDe,
                                             completion: onDownloadableRegionsCompleted)
}

// Completion handler to receive search results.
func onDownloadableRegionsCompleted(error: MapLoaderError?, regions: [Region]?) {
    if let mapLoaderError = error {
        self.showMessage("Downloadable regions error: \(mapLoaderError)")
        return
    }

    // If error is nil, it is guaranteed that the list will not be nil.
    downloadableRegions = regions!

    for region in downloadableRegions {
        print(region.name)
        guard let childRegions = region.childRegions else {
            continue
        }

        // Note that this code ignores to list the children of the children (and so on).
        for childRegion in childRegions {
            let sizeOnDiskinMB = childRegion.sizeOnDiskInBytes / (1024 * 1024)
            print("Child region: \(childRegion.name), ID: \(childRegion.regionId.id), Size: \(sizeOnDiskinMB) MB")
        }
    }

    self.showMessage("Found \(downloadableRegions.count) continents with various countries. Full list: \(downloadableRegions.description).")
}

レスポンスにエラーまたは結果が含まれています:mapLoaderErrorregions リストは一度に nil にすることや、一度に nil 以外にすることはできません。

各地域には、子地域を含めることができます。たとえば、ヨーロッパにはドイツ、フランス、スイスの他にも多数の子地域が含まれています。sizeOnDiskInBytes パラメーターは、ダウンロード完了後にダウンロードした地図が解凍されたときに、デバイスのファイル システムでどのくらいの容量を占めるかを示します。デバイスで利用できる容量が限られている可能性があるため、ダウンロードを開始する前にこれをユーザーに表示することは理にかなっています。

getDownloadableRegions() メソッドはリクエストをキャンセルするための戻り値として TaskHandle を提供します。上記のコード スニペットではこれを使用せず、代わりにアンダースコア (「_=...」) を用いて契約を満たしています。

地域をダウンロードする

RegionId が分かれば、それを使ってマップ データのダウンロードを開始できます。各 Region インスタンスにはローカライズされた name とダウンロードされた地図のサイズなど、その他のデータが含まれています。マップ データをダウンロードすると、すべてのデータが圧縮され、ダウンロードが完了するとデバイスのディスクに自動的に解凍されます。

以下では、ダウンロードした地域のリストを検索して、スイスの Region 要素を見つけます。上記の手順では、地域リストをドイツ語にローカライズするようにリクエストしています。

// Finds a region in the downloaded region list.
// Note that we ignore children of children (and so on).
private func findRegion(localizedRegionName: String) -> Region? {
    var downloadableRegion: Region?
    for region in downloadableRegions {
        if region.name == localizedRegionName {
            downloadableRegion = region
            break
        }
        guard let childRegions = region.childRegions else {
            continue
        }
        for childRegion in childRegions {
            if childRegion.name == localizedRegionName {
                downloadableRegion = childRegion
                break
            }
        }
    }

    return downloadableRegion
}

Regionが分かったら、そのRegionIdを使用してダウンロードを開始できます。一意のIDをリストに渡すので、同じリクエストで複数の地域をダウンロードできます。

特に大きな地域や複数の地域を一度にダウンロードする場合など、複数の地域を同時にダウンロードすると大量のRAMが消費される可能性があります。メモリ使用量が多くなると、システムリソースを解放するために、オペレーティングシステムによってアプリケーションが強制的に終了される場合があります。アプリが強制的に終了されないようにするには、地域を順番に (一度に1つずつ) ダウンロードするか、デバイスの使用可能なメモリとダウンロードする地域のサイズに応じて同時ダウンロード数を制限します。

ここでは、1つの地域のみをダウンロードします。

func onDownloadMapClicked() {
    // Find region for Switzerland using the German name as identifier.
    // Note that we requested the list of regions in German above.
    let swizNameInGerman = "Schweiz"
    let swizRegion = findRegion(localizedRegionName: swizNameInGerman)

    guard let region = swizRegion else {
        showMessage("Error: The Swiz region was not found. Click 'Regions' first to download the list of regions.")
        return
    }

    // For this example we only download one country.
    let regionIDs = [region.regionId]
    let mapDownloaderTask = mapDownloader.downloadRegions(regions: regionIDs,
                                                          statusListener: self)
    mapDownloaderTasks.append(mapDownloaderTask)
}

// Conform to the DownloadRegionsStatusListener protocol.
func onDownloadRegionsComplete(error: MapLoaderError?, regions: [RegionId]?) {
    if let mapLoaderError = error {
        self.showMessage("Download regions completion error: \(mapLoaderError)")
        return
    }

    // If error is nil, it is guaranteed that the list will not be nil.
    // For this example we downloaded only one hardcoded region.
    showMessage("Map download completed 100% for Switzerland! ID: \(String(describing: regions!.first))")
}

// Conform to the DownloadRegionsStatusListener protocol.
func onProgress(region: RegionId, percentage: Int32) {
    showMessage("Map download progress for Switzerland. ID: \(region.id). Progress: \(percentage)%.")
}

// Conform to the DownloadRegionsStatusListener protocol.
func onPause(error: MapLoaderError?) {
    if (error == nil) {
        showMessage("The download was paused by the user calling mapDownloaderTask.pause().")
    } else {
        showMessage("Download regions onPause error. The task tried to often to retry the download: \(error.debugDescription)")
    }
}

// Conform to the DownloadRegionsStatusListener protocol.
func onResume() {
    showMessage("A previously paused download has been resumed.")
}

DownloadRegionsStatusListener は、4 つのイベントを提供しています。2 番目は進行中のダウンロードの進行状況を示し、1 番目は、ダウンロードが完了したことを通知します。ダウンロードは MapLoaderError で完了することもあります。そのため、問題が発生していないかを確認するとよいでしょう。

onDownloadRegionsComplete() のレスポンスにエラーまたは結果が含まれています:mapLoaderErrorregions リストは一度に nil にすることや、一度に nil 以外にすることはできません。

pause イベントは、ダウンロードがユーザーまたはタスク自体によって一時停止されたときに通知されます。内部的には、HERE SDK は地域のダウンロードがネットワーク接続不良で中断された場合、再試行します。これが頻繁に発生する場合、onPause()MapLoaderError が設定され、ダウンロードが一時停止します。一時停止された MapDownloaderTask はユーザーのみが再開でき、これは関連イベントによっても示されます。特に大規模な地域の場合、たとえば、接続が改善されるまでダウンロードを一時停止する方が便利な場合があります。ダウンロードが再開されると、ダウンロードは停止した時点から続行され、ダウンロード済みのマップ データは失われません。一時停止した地域に対して downloadRegions() をコールすると、元のタスクで resume() をコールしたのと同じ効果があり、進行状況は中断したところから継続します。

ダウンロードを開始した後、進行中の非同期ダウンロード操作をキャンセルできるように、即時の戻り値を取得します。ユーザーが上記のコードを複数回トリガーする可能性があるため、上記では MapDownloaderTask がリストに保存されています。

進行中のダウンロードをすべてキャンセルするには、以下のコード スニペットを使用できます。

func onCancelMapDownloadClicked() {
    for mapDownloaderTask in mapDownloaderTasks {
        mapDownloaderTask.cancel()
    }
    showMessage("Cancelled \(mapDownloaderTasks.count) download tasks in list.")
    mapDownloaderTasks.removeAll()
}

キャンセルされた MapDownloaderTask は再開できないことに注意してください。ただし、再度新しくダウンロード リクエストを開始することはできます。

「OfflineMap」のサンプル アプリは GitHub にあります。

ダウンロードしたリージョンを削除する

次のコードスニペットは、インストールされているすべてのリージョンをMapDownloaderを使用して削除する方法を示しています。

  func deleteDownloadedRegion() {
      var installedRegionList: [InstalledRegion] = getInstalledRegionsList()

      // Retrieving the RegionIds from the list of installed regions, which will be used for the deletion process.
      let regionIds: [RegionId] = installedRegionList.map {$0.regionId}

      // Asynchronous operation to delete map data for regions specified by a list of RegionId.
      // Deleting a region when there is a pending download returns error MapLoaderError.INTERNAL_ERROR.
      // Also, deleting a region when there is an ongoing download returns error MapLoaderError.NOT_READY
      mapDownloader?.deleteRegions(regions: regionIds) { [self] error, deletedRegions in
          // When error is null, the deletedRegions list is guaranteed to be non-null.
          if error == nil, let deletedRegions = deletedRegions {
              for regionId in deletedRegions {
                  print("Successfully deleted regions: \(regionId)")
              }
              showMessage("Successfully deleted regions.")
          } else {
              print("Deleting regions failed: \(String(describing: error))")
              showMessage("Deleting regions failed: \(String(describing: error))")
          }
      }
  }

削除する前に、削除するリージョンのダウンロードが進行中でないことを確認してください。進行中または保留中のダウンロードがあるリージョンを削除しようとすると、MapLoaderError.notReadyMapLoaderError.internalError.などのエラーが発生する可能性があります。

選択したリージョンのみを削除する場合は、RegionIdごとに人間が読める名前をアプリケーションに保存する必要があるかもしれません。

壊れた地図を修復する

アプリがバックグラウンドで実行されている間にマップデータのダウンロードや更新を続けることは推奨されません。上記のセクションに示されているとおり、HERE SDK はダウンロードの pause()resume() のメソッドを提供しています。たとえば、アプリがバックグラウンドに移行したり、再開されたりした場合です。このような場合は、ユーザーに通知することをお勧めします。

ただし、クラッシュなどにより、地図更新操作が完了する前にアプリが終了してしまうことがあります。そのため、最悪の場合、デバイスのディスクに中間状態が発生する可能性があります。

HERE SDK では getInitialPersistentMapStatus() メソッドでそのような問題をチェックする便利な方法を提供しています。また、可能であれば、壊れた地図を修復できます。

private func checkInstallationStatus() {
    // Note that this value will not change during the lifetime of an app.
    let persistentMapStatus = mapDownloader.getInitialPersistentMapStatus()
    if persistentMapStatus != PersistentMapStatus.ok {
        // Something went wrong after the app was closed the last time. It seems the offline map data is
        // corrupted. This can eventually happen, when an ongoing map download was interrupted due to a crash.
        print("PersistentMapStatus: The persistent map data seems to be corrupted. Trying to repair.")

        // Let's try to repair.
        mapDownloader.repairPersistentMap(completion: onMapRepairCompleted)
    }
}

// Completion handler to get notified whether map reparation was successful or not.
private func onMapRepairCompleted(persistentMapRepairError: PersistentMapRepairError?) {
    if persistentMapRepairError == nil {
        print("RepairPersistentMap: Repair operation completed successfully!")
        return
    }

    // In this case, check the PersistentMapStatus and the recommended
    // healing option listed in the API Reference. For example, if the status
    // is "pendingUpdate", it cannot be repaired, but instead an update
    // should be executed. It is recommended to inform your users to
    // perform the recommended action.
    print("RepairPersistentMap: Repair operation failed: \(String(describing: persistentMapRepairError))")
}

ダウンロードしたマップ データに問題がある可能性があることをユーザーに通知することをお勧めします。このようなダイアログは、修復操作を実行する前、または修復操作が失敗した場合に必要となる可能性がある他のフォローアップ アクションを実行する前に、アプリ側で表示できます。ただし、getInitialPersistentMapStatus()repairPersistentMap() の呼び出しをサイレントに実行して、そのような通知が必要かどうかを確認できます。

最悪の場合、修復操作が失敗すると、マップ データを削除して再度ダウンロードする必要があります。deleteRegions() をプログラムで呼び出し、clearCache​() を呼び出して、SDKCache 経由でマップ キャッシュのクリアを試すことができます。この場合、ユーザーに通知し、アプリケーションを再起動することをお勧めします。

または、データを手動で削除することもできます。ダウンロードされた地域とキャッシュのパスは、SDKOptions から persistentMapStoragePathcachePath のプロパティを介して取得できます。


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