代替オプション
マップデータをRegionsとして永続ストレージにインストールする以外に、MapDownloaderを使うことなく、データをキャッシュにプリフェッチすることもできます。
マップデータをプリフェッチする
HERE SDK は、マップ データのルート プリフェッチのサポートを提供します。これにより、ユーザーエクスペリエンスが向上し、たとえば、ターン・バイ・ターンナビ中の一時的なネットワーク損失を適切に処理できます。
移動する地域のオフライン マップがすでにダウンロードされている場合、これは必要ありません。その場合はすべてのマップデータがすでに存在するため、ネットワーク接続は必要ありません。専用の OfflineRoutingEngine などとは異なり、Navigator または VisualNavigator では、キャッシュに保存されたデータまたはオフライン マップ データにフォールバックする必要があるかどうかが自動的に決定されます。一般に、マップ ビューを表示せずにヘッドレスでナビゲーションを実行する場合でも、マップ データは必要です。その理由は、マップ マッチングのためにナビゲーション中にマップ データにアクセスしたり、制限速度などの特定の道路属性について通知したりする必要があるためです。このデータは、デバイス上の利用可能なデータから取得されます。デバイス上にない場合は、ナビゲーション中にダウンロードする必要があります。そのため、先の道路を見越してデータをより多くプリフェッチすると役立ちます。プリフェッチしておかないと、一時的なネットワーク接続の損失を適切に処理できなくなる可能性があります。
注これはこの機能のベータリリースであるため、いくつかのバグや予期しない動作が発生する可能性があります。関連するAPIは、廃止のプロセスを経ずに、新しいリリースに変更される可能性があります。
RoutePrefetcher コンストラクタには、唯一のパラメーターとして SDKNativeEngine インスタンスが必要です。HERE SDK の初期化が完了した後、SDKNativeEngine.getSharedInstance() から取得できます。
RoutePrefetcher では、マップ データを事前にダウンロードすることができます。マップ データはマップ キャッシュに読み込まれます。マップキャッシュには独自のサイズ制限があり、すでにデータが含まれている可能性があります。RoutePrefetcherでは、新しいマップデータを保存するために過去にキャッシュに保存されたデータの削除が必要になる場合があります。
- 移動を開始する前に、出発地点周辺で一度プリフェッチすることをお勧めします。現在位置の周囲に
GeoCircleを作成し、それをGeoPolygonで囲み、polygonPrefetcher.prefetch(...)呼び出してタイルをマップキャッシュに読み込みます。
double radiusInMeters = 2000.0;
GeoCircle geoCircle = new GeoCircle(currentGeoCoordinates, radiusInMeters);
polygonPrefetcher.prefetch(new GeoPolygon(geoCircle), new PrefetchStatusListener() {
@Override
public void onProgress(int percentage) {
messageView.setText("Prefetch progress: " + percentage + "%");
}
@Override
public void onComplete(@Nullable MapLoaderError mapLoaderError) {
if (mapLoaderError == null) {
messageView.setText("Prefetch completed successfully");
} else {
messageView.setText("Prefetch failed: " + mapLoaderError);
}
}
});
この呼び出しは、指定された場所の周辺のマップデータをマップキャッシュにプリフェッチし、ユーザーがルートの追随を開始したときにユーザーの現在の位置からルートが開始すると推測して、十分なマップデータを利用できるようにします。
プリフェッチは非同期で実行され、実行スレッドに空きがある場合にのみ開始されるため、アプリ全体のパフォーマンスへの影響は最小限に抑えられます。
- ナビゲーションが開始した後に、
routePrefetcher.prefetchAroundRouteOnIntervals(navigator)を1回呼び出すことを検討してください。提供されたNavigatorインスタンスに現在設定されているルート上の軌道のマップ データをプリフェッチします。ルートが設定されていない場合、データはプリフェッチされません。ルートの軌道のデフォルトの長さは 10 km、幅は 5 km です。マップ データは、離散的な間隔でのみプリフェッチされます。プリフェッチは、現在の軌道の終わりに到達する 1 km 手前で開始します。プリフェッチは、RouteProgressイベントによって示される、現在のマップマッチングした場所に基づいて行われます。最初のプリフェッチは、ルート上を 9 km 移動した後に開始されます。新しいルートをnavigatorに設定しても、このメソッドを再度呼び出す必要はありません。ただし、2回以上呼び出したとしても悪い影響はありません。
RoutePrefetcherにはSDKNativeEngineインスタンスが必要です。こちらが作成方法になります。
RoutePrefetcher routePrefetcher = new RoutePrefetcher(SDKNativeEngine.getSharedInstance());
val routePrefetcher = new RoutePrefetcher(SDKNativeEngine.getSharedInstance()!!)
円形のエリアをプリフェッチする方法の例を以下に示します。
private void prefetchMapData(GeoCoordinates currentGeoCoordinates) {
// Prefetches map data around the provided location with a radius of 2 km into the map cache.
double radiusInMeters = 2000.0;
GeoCircle geoCircle = new GeoCircle(currentGeoCoordinates, radiusInMeters);
polygonPrefetcher.prefetch(new GeoPolygon(geoCircle), new PrefetchStatusListener() {
@Override
public void onProgress(int percentage) {
messageView.setText("Prefetch progress: " + percentage + "%");
}
@Override
public void onComplete(@Nullable MapLoaderError mapLoaderError) {
if (mapLoaderError == null) {
messageView.setText("Prefetch completed successfully");
} else {
messageView.setText("Prefetch failed: " + mapLoaderError);
}
}
});
}
private fun prefetchMapData(currentGeoCoordinates: GeoCoordinates) {
// Prefetches map data around the provided location with a radius of 2 km into the map cache.
val radiusInMeters = 2000.0
val geoPolygon = GeoPolygon(GeoCircle(currentGeoCoordinates, radiusInMeters))
val polygonPrefetcher= PolygonPrefetcher(SDKNativeEngine.getSharedInstance()!!)
polygonPrefetcher.prefetch(geoPolygon, object : PrefetchStatusListener {
override fun onProgress(progress: Int) {
Log.d(TAG, "Polygon prefetch progress: $progress %")
}
override fun onComplete(error: MapLoaderError?) {
if (error == null) {
Log.d(TAG, "Polygon prefetch completed successfully")
}
else {
Log.d(TAG, "Polygon prefetch failed: ${error.name}")
}
}
})
}
ルートを追随している間の定期的なプリフェッチは、routePrefetcher.prefetchAroundRouteOnIntervals(...)を使用して実行できます。ルートが設定されていない場合、データはプリフェッチされません。次のようにして実行できます。
// Prefetches map data within a corridor along the route that is currently set to the provided Navigator instance.
routePrefetcher.prefetchAroundRouteOnIntervals(visualNavigator);
// Prefetches map data within a corridor along the route that is currently set to the provided Navigator instance.
routePrefetcher.prefetchAroundRouteOnIntervals(visualNavigator)
定期的なプリフェッチを停止するには、次のようにします。
routePrefetcher.stopPrefetchAroundRoute();
routePrefetcher.stopPrefetchAroundRoute()
ルートの開始時にRoutePrefetcherが正常に使用され、その後接続が失われた場合、キャッシュに保存されたデータはマップキャッシュが削除されるまで、その先の電源サイクルでも保持されます。マップ キャッシュの削除ポリシーの詳細については、こちらを参照してください。
- 利便性のためナビゲーションを開始する前に両方のメソッドを同時に呼び出すこともできます。ただしその代わりに、その後すぐに移動が開始される場合は、必要なすべてのデータをプリフェッチする十分な時間がない可能性があります。
prefetchAroundRouteOnIntervals()はガイダンス中のネットワーク トラフィックを絶えず増加させます。
もちろん、プリフェッチされたデータがなくてもガイダンスは可能ですが、エクスペリエンスの十分に最適化されない可能性があります。
どちらの呼び出しも、キャッシュに保存されたマップ データを利用する一時的なオフライン ユースケースの最適化に役立ちます。prefetchAroundLocationWithRadius() はナビゲーションのユースケース以外にも使用できますが、prefetchAroundRouteOnIntervals() には進行中のナビゲーション シナリオが必要です。
別の方法として、ルート全体のマップ データを事前にプリフェッチすることもできます。RoutePrefetcher.prefetchGeoCorridor() を使用して、ルート形状から作成された GeoCorridor のタイル データをプリフェッチします。これにはルートの長さ、軌道の幅、ネットワークによって少し時間がかかることがあるため、進捗状況が PrefetchStatusListener.onProgress() でレポートされます。操作が完了すると、PrefetchStatusListener.onComplete() イベントが送信されます。
注ルートですでにダウンロードされた
Regionデータが渡される場合、軌道のこれらの部分は再利用され、再度ダウンロードされません。
同様に、プリフェッチでは、マップ キャッシュにすでにあるデータが 2 回ダウンロードされることはありません。一般に、プリフェッチされたすべてのデータがデバイスに永続的に保存されるわけではなく、後で新しいデータが読み込まれると、削除される可能性があります。また、空き容量は、ユーザーが設定できるマップ キャッシュ サイズによって異なります。十分な空き容量がない場合は、MapLoaderErrorが発生します。マップ キャッシュの詳細については、こちらを参照してください。
マップ データをプリフェッチするもう 1 つの方法は、PolygonPrefetcher を使用して実装できます。この方法では、実在の Route から独立したデータを取得できます。代わりに、データをダウンロードする場所を指定するために GeoPolygon のみが必要です。このデータはマップ キャッシュに一時的にのみ保存されます。その点は、RoutePrefetcher と同様です。PolygonPrefetcherでは、データをダウンロードする前に、ダウンロードの予想されるMapDataSizeを見積もることもできます。
プリフェッチの進行状況の監視
PrefetchStatusListenerインターフェースは、プリフェッチ操作の進行状況と完了ステータス監視するためのコールバックを提供します。これは広範囲のプリフェッチを行う際に特に便利であり、ユーザーへのフィードバックを提供したり、完了イベントを適切に処理したりすることが可能になります。
このインターフェースでは、次の2つのメソッドが定義されています。
onProgress(int percentage):プリフェッチ操作中に複数回呼び出され、ダウンロードの進行状況をパーセント (0~100%) で報告します。onComplete(MapLoaderError error):プリフェッチ操作が正常に完了した場合 (errorがnull)またはエラーが発生した場合に通知されます。
円形のエリアをプリフェッチする際に、PrefetchStatusListenerを実装する手順を以下い示します。
private void prefetchMapDataWithProgress(GeoCoordinates currentGeoCoordinates) {
double radiusInMeters = 12000.0;
GeoCircle geoCircle = new GeoCircle(currentGeoCoordinates, radiusInMeters);
polygonPrefetcher.prefetch(new GeoPolygon(geoCircle), new PrefetchStatusListener() {
@Override
public void onProgress(int percentage) {
messageView.setText("Prefetch progress: " + percentage + "%");
}
@Override
public void onComplete(@Nullable MapLoaderError mapLoaderError) {
if (mapLoaderError == null) {
messageView.setText("Prefetch completed successfully");
} else {
messageView.setText("Prefetch failed: " + mapLoaderError);
}
}
});
}
コールバックメソッドはいずれもメインスレッド上で呼び出されるため、これらのコールバック内でUI要素を直接更新しても安全です。PrefetchStatusListenerはPolygonPrefetcher.prefetch()やRoutePrefetcher.prefetchGeoCorridor()など、進行状況の報告をサポートする操作で使用できます。
詳しくは、Java版とKotlin版の両方を利用できるNavigationサンプルアプリを参照してください。
エリアをダウンロードする
事前定義されたRegionをダウンロードする代わりに、カスタムエリアを指定してダウンロードすることもできます。このエリアはポリゴンとして設定することも、たとえば、以下に示すようにGeoBoxとして設定することもできます。
HERE SDKは、そのエリアが以前に別のエリアやRegionの一部としてダウンロードされていた場合でも、マップデータを一度保存するだけでストレージを最適化します。
Regionデータのダウンロードに使用されるDownloadRegionsStatusListenerは、エリアのダウンロードにも使用されます。インストールの進行状況についても同様の方法で更新を提供します。エリアのダウンロードが完了すると、リストにはそのエリアを特定する一意のRegionIdが1つだけ含まれます。このIDを人間が読める名前で保存することをお勧めします。そうすることで、将来的にmapDownloader.deleteRegions(...)を呼び出してダウンロード済みのエリアを削除するのが容易になります。ダウンロードしたエリアのIDには、InstalledRegionsを通じてアクセスできます。
注
mapDownloader.downloadArea(...)を並列で呼び出して、複数のエリアを同時にダウンロードできます。
ビューポートに現在表示されているエリアを永続的にインストールする方法の例を次に示します。
public void onDownloadAreaClicked() {
showDialog("Note", "Downloading the area that is currently visible in the viewport.");
GeoPolygon polygonArea = new GeoPolygon(getMapViewGeoBox());
mapDownloader.downloadArea(polygonArea, new DownloadRegionsStatusListener() {
@Override
public void onDownloadRegionsComplete(@Nullable MapLoaderError mapLoaderError, @Nullable List<RegionId> list) {
if (mapLoaderError != null) {
String message = "Download area completion error: " + mapLoaderError;
snackbar.setText(message).show();
return;
}
// If error is null, it is guaranteed that the regions will not be null.
String message = "Completed 100% for area! ID: " + list.get(0).id;
snackbar.setText(message).show();
Log.d(TAG, message);
}
@Override
public void onProgress(@NonNull RegionId regionId, int percentage) {
// Note that this ID is uniquely created and can be to delete the area in the future.
String message = "Download for area ID: " + regionId.id +
". Progress: " + percentage + "%.";
snackbar.setText(message).show();
}
@Override
public void onPause(@Nullable MapLoaderError mapLoaderError) {
if (mapLoaderError == null) {
String message = "The download area was paused by the user calling mapDownloaderTask.pause().";
snackbar.setText(message).show();
} else {
String message = "Download area 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 area download has been resumed.";
snackbar.setText(message).show();
}
});
}
// Download the rectangular area that is currently visible in the viewport.
// It is possible to call this method in parallel to download multiple areas in parallel.
fun onDownloadAreaClicked() {
showDialog("Note", "Downloading the area that is currently visible in the viewport.")
val polygonArea = GeoPolygon(getMapViewGeoBox())
mapDownloader.downloadArea(polygonArea, object : DownloadRegionsStatusListener {
override fun onDownloadRegionsComplete(
mapLoaderError: MapLoaderError?,
list: List<RegionId>?
) {
if (mapLoaderError != null) {
val message = "Download area completion error: $mapLoaderError"
snackbar.show(message)
return
}
// If error is null, it is guaranteed that the regions will not be null.
val message = "Completed 100% for area! ID: " + list!![0].id
snackbar.show(message)
Log.d(TAG, message)
}
override fun onProgress(regionId: RegionId, percentage: Int) {
// Note that this ID is uniquely created and can be to delete the area in the future.
val message = "Download for area ID: " + regionId.id +
". Progress: " + percentage + "%."
snackbar.show(message)
}
override fun onPause(mapLoaderError: MapLoaderError?) {
if (mapLoaderError == null) {
val message =
"The download area was paused by the user calling mapDownloaderTask.pause()."
snackbar.show(message)
} else {
val message =
"Download area onPause error. The task tried to often to retry the download: $mapLoaderError"
snackbar.show(message)
}
}
override fun onResume() {
val message = "A previously paused area download has been resumed."
snackbar.show(message)
}
})
}
この例では、GeoBoxを使用してダウンロードするリージョンを定義します。上記のコードには、getMapViewGeoBox()のコードが含まれており、GitHubにある付属の「OfflineMaps」サンプルアプリで確認できます。
インストールされているリージョンのリストを取得する
MapDownloaderを使用して、InstalledRegion要素のリストを取得し、現在デバイスにインストールされているリージョンを確認できます。インストールステータスを確認することもできます。region.sizeOnDiskInBytesを使用し、次に示すように、ストレージの合計使用量を計算できます。
// Get the list of all downloaded regions.
private List<InstalledRegion> getInstalledRegionList() {
List<InstalledRegion> installedRegionList = new ArrayList<>();
try {
installedRegionList = mapDownloader.getInstalledRegions();
} catch (MapLoaderException e) {
Log.d("Fetching installedRegions failed", e.error.toString());
}
return installedRegionList;
}
// Log all the regions downloaded and total storage usage of downloaded map areas.
private void logInstalledRegions() {
List<InstalledRegion> installedRegionList = getInstalledRegionList();
for (InstalledRegion region : installedRegionList) {
Log.d("Installed region", "Downloaded region id: " + region.regionId);
Log.d("Installed region", "sizeOnDiskInBytes: " + region.sizeOnDiskInBytes);
Log.d("Installed region", "InstalledRegionStatus: " + region.status.toString());
}
long occupiedStorageSize = getSizeOfInstalledRegionsInBytes(installedRegionList);
Log.d("Installed Region", "Total storage size in bytes: " + occupiedStorageSize);
}
private long getSizeOfInstalledRegionsInBytes(List<InstalledRegion> installedRegionList) {
return installedRegionList.stream()
.mapToLong(region -> region.sizeOnDiskInBytes)
.sum();
}
private fun getInstalledRegionList(): List<InstalledRegion> {
var installedRegionList: List<InstalledRegion> = ArrayList()
try {
installedRegionList = mapDownloader.getInstalledRegions()
} catch (e: MapLoaderException) {
Log.d("Fetching installedRegions failed", e.error.toString())
}
return installedRegionList
}
// Log all the regions downloaded and total storage usage of downloaded map areas.
private fun logInstalledRegions() {
val installedRegionList = getInstalledRegionList()
for (region in installedRegionList) {
Log.d("Installed region", "Downloaded region id: " + region.regionId)
Log.d("Installed region", "sizeOnDiskInBytes: " + region.sizeOnDiskInBytes)
Log.d("Installed region", "InstalledRegionStatus: " + region.status.toString())
}
val occupiedStorageSize = getSizeOfInstalledRegionsInBytes(installedRegionList)
Log.d("Installed Region", "Total storage size in bytes: $occupiedStorageSize")
}
private fun getSizeOfInstalledRegionsInBytes(installedRegionList: List<InstalledRegion>): Long {
return installedRegionList.stream()
.mapToLong { region: InstalledRegion -> region.sizeOnDiskInBytes }
.sum()
}
7 日前の更新










