マップデータをインストールする
以下では、MapDownloaderを使用してオフラインマップデータをアプリケーションに統合する方法を説明します。
Region地物のリストをダウンロードします。オプションで、このリストを現地の地域名を使用してローカライズできます。このリストを取得したら、ダウンロードするRegionIdを選択し、それをダウンロード リクエストとしてMapDownloaderに渡すことができます。MapDownloaderを使用して、単一のRegionまたは地域のリストをダウンロードします。複数の地域を並行してダウンロードすることもできます。DownloadRegionsStatusListenerを設定して、ダウンロードの進行状況をユーザーに表示します。RAMの負荷を下げるために、このようなデバイスでは順番にダウンロードすることを検討してください。MapUpdaterを使用して、ダウンロード済みの地域とマップキャッシュを新しいマップバージョンに更新します。
ダウンロードが完了すると、地図を使用できるようになります。デバイスがオフラインの場合、カメラのターゲットがその地域に向けられると、ダウンロードした地域が自動的に表示されます。
注ダウンロードが失敗した場合でも、HERE SDK は完全に動作可能な状態のままです。進行状況が 100% に達し、最終的に操作が完了したことを示すステータスが表示されるまで、再度ダウンロードをお試しください。問題がより深刻な場合は、修復を試してください。詳細については、以下の修復のセクションを参照してください。
アプリがバックグラウンドで実行されている間にマップデータのダウンロードや更新を続けることは推奨されません。ベストプラクティスとして、ユーザーがアプリをフォアグラウンドで実行し続けられない場合には、継続中のダウンロードを
pause()またはcancel()するように通知します。また、アプリが一時停止後に再開される際にresume()オプションを提供することを検討してください。デバイス (特に GPU と CPU) をアクティブにし続け、アプリがアイドル状態にならないようにするために、プラットフォーム固有のコードを記述することを検討します。または、アプリの機能を維持するために画面をオンのままにしておくようユーザーに通知します。
地域、国、大陸全体のマップ データは数百メガバイトになる場合があるため、利用可能な帯域幅などの要因によってはダウンロードにかなりの時間がかかる可能性があります。さらに、接続がタイムアウトして回復できない場合、ダウンロードが失敗することがあります。ユーザーの操作性を向上するために、ユーザーが進行中の操作をキャンセルできるようにし、地図のダウンロードが成功するまで進行状況を監視できるようにすることをお勧めします。
注これらの機能について概要を簡単に確認するには、Java版とKotlin版が提供されているOfflineMapsExampleクラスを参照してください。これには以下に示すすべてのコードスニペットが含まれており、GitHubにあるJava版およびKotlin版の「OfflineMaps」のサンプルアプリの一部です。
MapDownloader を使用する
SDKNativeEngine ごとに MapDownloader を 1 回使用できます。
SDKNativeEngine sdkNativeEngine = SDKNativeEngine.getSharedInstance();
if (sdkNativeEngine == null) {
throw new RuntimeException("SDKNativeEngine not initialized.");
}
// Create MapDownloader in background to not block the UI thread.
MapDownloader.fromEngineAsync(sdkNativeEngine, new MapDownloaderConstructionCallback() {
@Override
public void onMapDownloaderConstructedCompleted(@NonNull MapDownloader mapDownloader) {
// ...
}
});
val sdkNativeEngine = SDKNativeEngine.getSharedInstance()
?: throw RuntimeException("SDKNativeEngine not initialized.")
// Create MapDownloader in background to not block the UI thread.
MapUpdater.fromEngineAsync(sdkNativeEngine, object : MapUpdaterConstructionCallback {
override fun onMapUpdaterConstructe(mapUpdater: MapUpdater) {
// ...
}
})
通常、アプリを起動すると SDKNativeEngine が自動的に初期化され、MapView が表示されます。したがって、実行時にそのインスタンスにアクセスし、そこから MapDownloader を取得できます。
注すべてのインスタンスを同期して確実に使用できるように、
ExecutorServiceなどの専用のローダー メソッドまたはクラスの使用を検討します。この処理中に読み込みインジケーターを表示すると、ユーザー エクスペリエンスが向上します。このアプローチにより、null チェックを行わずにクラス内でインスタンスを自信を持って使用できるようになります。通常、MapDownloaderとMapUpdaterの作成 (以下参照) はごくわずかな時間で完了します。
デフォルトでは、ダウンロードしたマップ データはデフォルトの場所に保存されます。
// Note that the default storage path can be adapted when creating a new SDKNativeEngine.
String storagePath = SDKNativeEngine.getSharedInstance().getOptions().cachePath;
Log.d("", "StoragePath: " + storagePath);
// Note that the default storage paths can be adapted when creating a new SDKNativeEngine.
val storagePath = sdkNativeEngine.options.cachePath
Log.d(TAG, "Cache 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.
String persistentMapStoragePath = sdkNativeEngine.getOptions().persistentMapStoragePath;
Log.d(TAG, "PersistentMapStoragePath: " + persistentMapStoragePath);
// This is the default path for storing downloaded regions.
// The application must have read/write access to this path if updating it.
val persistentMapStoragePath = sdkNativeEngine.options.persistentMapStoragePath
Log.d(TAG, "PersistentMapStoragePath: $persistentMapStoragePath")
地域のリストをダウンロードする
ダウンロード可能な各 Region は一意の RegionId によって識別されます。どの地域が利用でき、どの RegionID がどの Region に属しているかを知るには、利用できるすべてのオフライン マップのリストをダウンロードする必要があります。これには全世界の地域が含まれています。
注各
Regionは複数の子を含むことができ、それぞれの子は親Regionのサブセットとなります。親をダウンロードすると、子地域が自動的に含まれます。地域のより狭い部分のみに興味がある場合は、子地域を横断できます。通常、トップレベルの地域は、国を子として持つ大陸を表します。分かりやすくするために、以下ではダウンロード可能な国のみを探し、子とその子の子 (など) は無視します。
以下のコードはダウンロードできる地域のリストをダウンロードし、後で使用できるように Region 要素をリストに保存します。
// Download a list of Region items that will tell us what map regions are available for later download.
mapDownloader.getDownloadableRegions(LanguageCode.DE_DE, new DownloadableRegionsCallback() {
@Override
public void onCompleted(@Nullable MapLoaderError mapLoaderError, @Nullable List<Region> list) {
if (mapLoaderError != null) {
String message = "Downloadable regions error: " + mapLoaderError;
snackbar.setText(message).show();
return;
}
// If error is null, it is guaranteed that the list will not be null.
downloadableRegions = list;
for (Region region : downloadableRegions) {
Log.d("RegionsCallback", region.name);
List<Region> childRegions = region.childRegions;
if (childRegions == null) {
continue;
}
// Note that this code ignores to list the children of the children (and so on).
for (Region childRegion : childRegions) {
long sizeOnDiskInMB = childRegion.sizeOnDiskInBytes / (1024 * 1024);
String logMessage = "Child region: " + childRegion.name +
", ID: "+ childRegion.regionId.id +
", Size: " + sizeOnDiskInMB + " MB";
Log.d("RegionsCallback", logMessage);
}
}
String message = "Found " + downloadableRegions.size() +
" continents with various countries. See log for details.";
snackbar.setText(message).show();
}
});
// Download a list of Region items that will tell us what map regions are available for later download.
mapDownloader.getDownloadableRegions(
LanguageCode.DE_DE,
object : DownloadableRegionsCallback {
override fun onCompleted(
maploaderError: MapLoaderError?,
regions: List<Region>?
) {
if (maploaderError != null) {
val message = "Downloadable regions error: $maploaderError"
snackbar.show(message)
return
}
// If error is null, it is guaranteed that the list will not be null.
downloadableRegions = regions!!
for (region in downloadableRegions) {
Log.d("RegionsCallback", region.name)
val childRegions = region.childRegions ?: continue
// Note that this code ignores to list the children of the children (and so on).
for (childRegion in childRegions) {
val sizeOnDiskInMB = childRegion.sizeOnDiskInBytes / (1024 * 1024)
val logMessage = "Child region: " + childRegion.name +
", ID: " + childRegion.regionId.id +
", Size: " + sizeOnDiskInMB + " MB"
Log.d("RegionsCallback", logMessage)
}
}
val message = "Found ${downloadableRegions.size} continents with various " +
"countries. Full list: $downloadableRegions."
snackbar.show(message)
}
}
)
注レスポンスにエラーまたは結果が含まれています。
MapLoaderErrorとList<Region>は一度に null にすることも、null 以外にすることもできません。
各地域には、子地域を含めることができます。たとえば、ヨーロッパにはドイツ、フランス、スイスの他にも多数の子地域が含まれています。sizeOnDiskInBytes パラメーターは、ダウンロード完了後にダウンロードした地図が解凍されたときに、デバイスのファイル システムでどのくらいの容量を占めるかを示します。デバイスで利用できる容量が限られている可能性があるため、ダウンロードを開始する前にこれをユーザーに表示することは理にかなっています。
地域をダウンロードする
RegionId が分かれば、それを使ってマップ データのダウンロードを開始できます。各 Region インスタンスにはローカライズされた name とダウンロードされた地図のサイズなど、その他のデータが含まれています。マップ データをダウンロードすると、すべてのデータが圧縮され、ダウンロードが完了するとデバイスのディスクに自動的に解凍されます。
以下では、ダウンロードした地域のリストを検索して、スイスの Region 要素を見つけます。上記の手順では、地域リストをドイツ語にローカライズするようにリクエストしています。
// Finds a region in the downloaded region list.
// Note that we ignore children of children (and so on).
private Region findRegion(String localizedRegionName) {
Region downloadableRegion = null;
for (Region region : downloadableRegions) {
if (region.name.equals(localizedRegionName)) {
downloadableRegion = region;
break;
}
List<Region> childRegions = region.childRegions;
if (childRegions == null) {
continue;
}
for (Region childRegion : childRegions) {
if (childRegion.name.equals(localizedRegionName)) {
downloadableRegion = childRegion;
break;
}
}
}
return downloadableRegion;
}
// Finds a region in the downloaded region list.
// Note that we ignore children of children (and so on).
private fun findRegion(localizedRegionName: String): Region? {
var downloadableRegion: Region? = null
for (region in downloadableRegions!!) {
if (region.name == localizedRegionName) {
downloadableRegion = region
break
}
val childRegions = region.childRegions ?: continue
for (childRegion in childRegions) {
if (childRegion.name == localizedRegionName) {
downloadableRegion = childRegion
break
}
}
}
return downloadableRegion
}
Regionが分かったら、そのRegionIdを使用してダウンロードを開始できます。一意のIDをリストに渡すので、同じリクエストで複数の地域をダウンロードできます。
注特に大きな地域や複数の地域を一度にダウンロードする場合など、複数の地域を同時にダウンロードすると大量のRAMが消費される可能性があります。メモリ使用量が多くなると、システムリソースを解放するために、オペレーティングシステムによってアプリケーションが強制的に終了される場合があります。アプリが強制的に終了されないようにするには、地域を順番に (一度に1つずつ) ダウンロードするか、デバイスの使用可能なメモリとダウンロードする地域のサイズに応じて同時ダウンロード数を制限します。
ここでは、1つの地域のみをダウンロードします。
// Find region for Switzerland using the German name as identifier.
// Note that we requested the list of regions in German above.
String swizNameInGerman = "Schweiz";
Region region = findRegion(swizNameInGerman);
if (region == null ) {
String message = "Error: The Swiz region was not found. Click 'Regions' first.";
snackbar.setText(message).show();
return;
}
// For this example we only download one country.
List<RegionId> regionIDs = Collections.singletonList(region.regionId);
MapDownloaderTask mapDownloaderTask = mapDownloader.downloadRegions(regionIDs,
new DownloadRegionsStatusListener() {
@Override
public void onDownloadRegionsComplete(@Nullable MapLoaderError mapLoaderError, @Nullable List<RegionId> list) {
if (mapLoaderError != null) {
String message = "Download regions completion error: " + mapLoaderError;
snackbar.setText(message).show();
return;
}
// If error is null, it is guaranteed that the list will not be null.
// For this example we downloaded only one hardcoded region.
String message = "Completed 100% for Switzerland! ID: " + list.get(0).id;
snackbar.setText(message).show();
}
@Override
public void onProgress(@NonNull RegionId regionId, int percentage) {
String message = "Download for Switzerland. ID: " + regionId.id +
". Progress: " + percentage + "%.";
snackbar.setText(message).show();
}
@Override
public void onPause(@Nullable MapLoaderError mapLoaderError) {
if (mapLoaderError == null) {
String message = "The download was paused by the user calling mapDownloaderTask.pause().";
snackbar.setText(message).show();
} else {
String message = "Download regions onPause error. The task tried to often to retry the download: " + mapLoaderError;
snackbar.setText(message).show();
}
}
@Override
public void onResume() {
String message = "A previously paused download has been resumed.";
snackbar.setText(message).show();
}
});
mapDownloaderTasks.add(mapDownloaderTask);
// Find region for Switzerland using the German name as identifier.
// Note that we requested the list of regions in German above.
val swizNameInGerman = "Schweiz"
val region = findRegion(swizNameInGerman)
if (region == null) {
val message = "Error: The Swiz region was not found. Click 'Regions' first."
snackbar.show(message)
return
}
// For this example we only download one country.
val regionIDs = listOf(region.regionId)
val mapDownloaderTask = mapDownloader.downloadRegions(
regionIDs,
object : DownloadRegionsStatusListener {
override fun onDownloadRegionsComplete(
mapLoaderError: MapLoaderError?,
list: List<RegionId>?
) {
if (mapLoaderError != null) {
val message =
"Download regions completion error: $mapLoaderError"
snackbar.show(message)
return
}
// If error is null, it is guaranteed that the list will not be null.
// For this example we downloaded only one hardcoded region.
val message = "Completed 100% for Switzerland! ID: " + list!![0].id
snackbar.show(message)
}
override fun onProgress(regionId: RegionId, percentage: Int) {
val message = "Download for Switzerland. ID: " + regionId.id +
". Progress: " + percentage + "%."
snackbar.show(message)
}
override fun onPause(mapLoaderError: MapLoaderError?) {
if (mapLoaderError == null) {
val message =
"The download was paused by the user calling mapDownloaderTask.pause()."
snackbar.show(message)
} else {
val message =
"Download regions onPause error. The task tried to often to retry the download: $mapLoaderError"
snackbar.show(message)
}
}
override fun onResume() {
val message = "A previously paused download has been resumed."
snackbar.show(message)
}
}
)
mapDownloaderTasks.add(mapDownloaderTask)
DownloadRegionsStatusListener は、4 つのイベントを提供しています。2 番目は進行中のダウンロードの進行状況を示し、1 番目は、ダウンロードが完了したことを通知します。ダウンロードは、MapLoaderErrorで完了すること場合もあるため、何か問題が発生したかどうかを確認すると良いでしょう。
注
onDownloadRegionsComplete()のレスポンスにエラーまたは結果が含まれています。MapLoaderErrorとList<RegionId>は一度に null にすることも、null 以外にすることもできません。
pause イベントは、ダウンロードがユーザーまたはタスク自体によって一時停止されたときに通知されます。内部的には、HERE SDK は地域のダウンロードがネットワーク接続不良で中断された場合、再試行します。これが頻繁に発生する場合、onPause() の MapLoaderError が設定され、ダウンロードが一時停止します。一時停止された MapDownloaderTask はユーザーのみが再開でき、これは関連イベントによっても示されます。特に大規模な地域の場合、たとえば、接続が改善されるまでダウンロードを一時停止する方が便利な場合があります。ダウンロードが再開されると、ダウンロードは停止した時点から続行され、ダウンロード済みのマップ データは失われません。一時停止した地域に対して downloadRegions() をコールすると、元のタスクで resume() をコールしたのと同じ効果があり、進行状況は中断したところから継続します。
ダウンロードを開始した後、進行中の非同期ダウンロード操作をキャンセルできるように、即時の戻り値を取得します。ユーザーが上記のコードを複数回トリガーする可能性があるため、上記では MapDownloaderTask がリストに保存されています。
進行中のダウンロードをすべてキャンセルするには、以下のコード スニペットを使用できます。
public void onCancelMapDownloadClicked() {
for (MapDownloaderTask mapDownloaderTask : mapDownloaderTasks) {
mapDownloaderTask.cancel();
}
String message = "Cancelled " + mapDownloaderTasks.size() + " download tasks in list.";
snackbar.setText(message).show();
mapDownloaderTasks.clear();
}
fun onCancelMapDownloadClicked() {
for (mapDownloaderTask in mapDownloaderTasks) {
mapDownloaderTask.cancel()
}
val message = "Cancelled " + mapDownloaderTasks.size + " download tasks in list."
snackbar.show(message)
mapDownloaderTasks.clear()
}
キャンセルされた MapDownloaderTask は再開できませんが、新しいダウンロード リクエストをあらためて開始できます。
注
ダウンロードしたリージョンを削除する
次のコードスニペットは、インストールされているすべてのリージョンをMapDownloaderを使用して削除する方法を示しています。
public void deleteInstalledRegions() {
List<InstalledRegion> installedRegionList = getInstalledRegionList();
// Retrieving the RegionIds from the list of installed regions, which will be used for the deletion process.
List<RegionId> regionIds = installedRegionList.stream()
.map(region -> region.regionId)
.collect(Collectors.toList());
// 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(regionIds, new DeletedRegionsCallback() {
@Override
public void onCompleted(@Nullable MapLoaderError mapLoaderError, @Nullable List<RegionId> list) {
// When error is null, the deletedRegions list is guaranteed to be non-null.
if (mapLoaderError == null && list != null) {
for (RegionId regionID : list) {
Log.d("deleteRegions", "Successfully deleted region: " + regionID.toString());
}
snackbar.setText("Successfully deleted regions!").show();
} else {
Log.e("deleteRegions", "Deleting regions failed:" + mapLoaderError.name());
snackbar.setText("Deleting regions failed: " + mapLoaderError.name()).show();
}
}
});
}
fun deleteInstalledRegions() {
val installedRegionList = getInstalledRegionList()
// Retrieving the RegionIds from the list of installed regions, which will be used for the deletion process.
val regionIds = installedRegionList.stream()
.map { region: InstalledRegion -> region.regionId }
.collect(Collectors.toList())
// 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(
regionIds
) { mapLoaderError, list -> // When error is null, the list is guaranteed to be not null.
if (mapLoaderError == null && list != null) {
for (regionID in list) {
Log.d(
"deleteRegions",
"Successfully deleted region: $regionID"
)
}
snackbar.show("Successfully deleted regions!")
} else {
Log.e(
"deleteRegions",
"Deleting regions failed:" + mapLoaderError!!.name
)
snackbar.show("Deleting regions failed: " + mapLoaderError.name)
}
}
}
削除する前に、削除するリージョンのダウンロードが進行中でないことを確認してください。進行中または保留中のダウンロードがある地域を削除しようとすると、MapLoaderError.NOT_READYやMapLoaderError.INTERNAL_ERRORなどのエラーが発生する可能性があります。
選択したリージョンのみを削除する場合は、RegionIdごとに人間が読める名前をアプリケーションに保存する必要があるかもしれません。
壊れた地図を修復する
アプリがバックグラウンドで実行されている間にマップデータのダウンロードや更新を続けることは推奨されません。前述のセクションに示されているとおり、HERE SDK はダウンロードの pause() と resume() のメソッドを提供しています。たとえば、アプリがバックグラウンドに移行したり、再開されたりした場合です。このような場合は、ユーザーに通知することをお勧めします。
ただし、クラッシュなどにより、地図更新操作が完了する前にアプリが終了してしまうことがあります。そのため、最悪の場合、デバイスのディスクに中間状態が発生する可能性があります。
HERE SDK では getInitialPersistentMapStatus() メソッドでそのような問題をチェックする便利な方法を提供しています。また、可能であれば、壊れた地図を修復できます。
private void checkInstallationStatus() {
// Note that this value will not change during the lifetime of an app.
PersistentMapStatus 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.
Log.d("PersistentMapStatus", "The persistent map data seems to be corrupted. Trying to repair.");
// Let's try to repair.
mapDownloader.repairPersistentMap(new RepairPersistentMapCallback() {
@Override
public void onCompleted(@Nullable PersistentMapRepairError persistentMapRepairError) {
if (persistentMapRepairError == null) {
Log.d("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 PENDING_UPDATE, it cannot be repaired, but instead an update
// should be executed. It is recommended to inform your users to
// perform the recommended action.
Log.d("RepairPersistentMap", "Repair operation failed: " + persistentMapRepairError.name());
}
});
}
}
private fun checkInstallationStatus() {
// Note that this value will not change during the lifetime of an app.
val 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.
Log.d(
"PersistentMapStatus",
"The persistent map data seems to be corrupted. Trying to repair."
)
// Let's try to repair.
mapDownloader.repairPersistentMap(object : RepairPersistentMapCallback {
override fun onCompleted(persistentMapRepairError: PersistentMapRepairError?) {
if (persistentMapRepairError == null) {
Log.d("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 PENDING_UPDATE, it cannot be repaired, but instead an update
// should be executed. It is recommended to inform your users to
// perform the recommended action.
Log.d(
"RepairPersistentMap",
"Repair operation failed: " + persistentMapRepairError.name
)
}
})
}
}
注ダウンロードしたマップ データに問題がある可能性があることをユーザーに通知することをお勧めします。このようなダイアログは、修復操作を実行する前、または修復操作が失敗した場合に必要となる可能性がある他のフォローアップ アクションを実行する前に、アプリ側で表示できます。ただし、
getInitialPersistentMapStatus()とrepairPersistentMap()の呼び出しをサイレントに実行して、そのような通知が必要かどうかを確認できます。
最悪の場合、修復操作が失敗すると、マップ データを削除して再度ダウンロードする必要があります。deleteRegions() をプログラムで呼び出し、clearCache() を呼び出して、SDKCache 経由でマップ キャッシュのクリアを試すことができます。この場合、ユーザーに通知し、アプリケーションを再起動することをお勧めします。
または、データを手動で削除することもできます。ダウンロードされた地域とキャッシュのパスは、SDKOptions から persistentMapStoragePath と cachePath のプロパティを介して取得できます。
7 日前の更新










