This is a blog series introducing features useful for map app development with Flutter. This time, we focus on displaying offline maps. We will implement a feature to download maps to the device and display them offline. In this article,Flutter map SDK (ArcGIS Maps SDK for Flutter) is used to show how to implement this feature.
Sample code is available on GitHub, so if you want to check it immediately, please download it. Instructions for running the app are described in the README. To run the sample, you will need to obtain an API key (free of charge) . For instructions from installing the map SDK to displaying the map, please see the blog article "Let's Create a Map App with Flutter!". This article focuses on the offline map display feature.
Offline Map (Vector Tile Layer) Creation
This time, we will create a feature to download data of any area of the map displayed in the app. First, we create a Vector Tile Layer that supports downloading so it can be used offline.
1. Sign in to ArcGIS Online ArcGIS Online account or ArcGIS Location Platform account. If you do not have an account, you can create one for free by following the steps hereの手順から無償で作成できます。
2. こちらのリンクを開き、ArcGIS Basemap Styles サービス (ベクター タイル レイヤー) のリストを表示します。
3. 名前に「(for Developers)」が付いているアイテムの中から、World Street Map (道路地図) や World Topographic Map (地形図) 等の任意のアイテムを選びます。選択したアイテムの名前部分をクリックして、左下に表示される [詳細の表示] をクリックします。
4. 右上に表示される [Vector Tile Style Editor で編集] をクリックします。Vector Tile Style Editor を使用することで、マップの用途に応じてオリジナルの見た目を設定できます。エディターの詳細については、こちらの記事もご覧ください。
5. 左上の [クイック編集] をクリックして、[言語] の設定を英語から日本語に変更します。
6. [名前を付けて保存] をクリックして、[名前を付けてスタイルを保存] 画面を表示します。
7. タイトルに適当な名前を記入し、[次のレイヤーと共有:] を「すべてのユーザー (パブリック)」に変更して、[スタイルの保存] をクリックします。
8. Web ブラウザーに表示されている URL の英数字の部分が、このベクター タイル レイヤー スタイルの ID です。この ID を後の手順で使用します。
オフラインマップの表示機能の実装
続いて、オフラインマップを表示するアプリを開発していきます。
1. 「Flutter で地図アプリを作成してみよう!」のブログ記事の手順 6 で、マップビュー コントローラーに作成したマップを設定してマップの表示までできることを確認します。
パッケージの追加
2. デバイス内のパスの取得に path_provider パッケージを使用するため、pubspec.yaml の dependencies セクションにパッケージを追加します。
dependencies:
flutter:
sdk: flutter
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.8
arcgis_maps: ^200.8.0+4672
# 追加
path_provider: ^2.1.5
3. lib/main.dart を開き、既に書かれているインポート行の後に、下記のコードを追加して、この後の処理に必要なパッケージをインポートします。
import 'package:path_provider/path_provider.dart';
import 'dart:io';
変数の追加
4. _MyHomePageState クラスの最初に、使用する変数を追加します。
// オンライン時に表示するマップ オブジェクト格納用
ArcGISMap? _onlineMap;
// ベクター タイルを表示するためのレイヤー オブジェクト
ArcGISVectorTiledLayer? _vectorTiledLayer;
// ベクター タイルをダウンロードする際に使用するジョブ
ExportVectorTilesJob? _exportVectorTilesJob;
// ローディングバー (LinearProgressIndicator) の表示フラグ
bool _loading = false;
UI の作成
5. オンラインマップとオフラインマップの表示を切り替えるボタンを追加します。build Widget の中に既にある Expanded ウィジェットの下に、下記を追加します。
children: [
Expanded(
child: Stack(children: [
ArcGISMapView(
・・・省略・・・
])),
// 追加
Row(
mainAxisAlignment: MainAxisAlignment.center,
// オンラインマップとオフラインマップの表示を切り替えるボタンを追加します。
children: [
ElevatedButton(
onPressed: takeOnline,
child: const Text('オンライン表示'),
),
SizedBox(width: 5.0),
ElevatedButton(
onPressed: takeOffline,
child: const Text('オフラインマップ作成'),
),
]
)
6. マップ上にインジケーター (LinearProgressIndicator) を表示するようにコードを変更します。
// 変更前
child: ArcGISMapView(
controllerProvider: () => _mapViewController,
onMapViewReady: _onMapViewReady,
),
// 変更後
child: Stack(children: [
ArcGISMapView(
controllerProvider: () => _mapViewController,
onMapViewReady: _onMapViewReady),
Visibility(
visible: _loading,
child: SizedBox.expand(
child: Container(
margin: EdgeInsets.all(20),
child: Center(
child: LinearProgressIndicator(
minHeight: 20,
),
),
),
),
),
])
ダウンロード機能の実装
7. ベースマップを作成する既存のコードを、「オフラインマップ (ベクター タイル レイヤー) の作成」で作成したベクター タイル レイヤーを表示するベースマップに変更します。PortalItem.withPortalAndItemId コンストラクターの itemId には、「オフラインマップ (ベクター タイル レイヤー) の作成」の手順.8 で表示された ID を設定します。
// 変更前
// ベースマップのラベルを日本語表記にするためのパラメーターを設定します。
final bsp = BasemapStyleParameters();
bsp.specificLanguage = "ja";
final basemap = Basemap.withStyle(BasemapStyle.arcGISStreets, parameters: bsp);
// 変更後
// ポータルのアイテム ID からベクター タイル レイヤーを作成します。
final portal = Portal(
Uri.parse('https://www.arcgis.com'),
);
final portalItem = PortalItem.withPortalAndItemId(
portal: portal, itemId: "aa3f471a985641e094549ef472adec18");
_vectorTiledLayer = ArcGISVectorTiledLayer.withItem(portalItem);
// ベクター タイル レイヤーをベースマップのレイヤーにしてマップを作成します。
final basemap = Basemap.withBaseLayer(_vectorTiledLayer,);
8. ここからは、マップをダウンロードする処理を実装していきます。「オフラインマップ作成」ボタンをタップした時に実行される takeOffline 関数 (ElevatedButton の onPressed プロパティに設定) を作成します。
// 「オフラインマップ作成」ボタンを選択したときの処理
void takeOffline() async {
if (_mapViewController.arcGISMap != _onlineMap) return;
}
9. takeOffline 関数内にコードを追加していきます。まず、処理中を示すためのインジケーターを表示します。
// インジケーターを表示します。
setState(() => _loading = true);
10. ベクター タイルをダウンロードするための、ExportVectorTilesTask を作成します。このタスクは、ベクター タイルのソースデータ (.vtpk ファイル) とスタイル リソースをローカルにダウンロードするためのタスクです。
// ダウンロードしたベクター タイルを保存するためのディレクトリを準備します。
final directory = await getApplicationDocumentsDirectory();
final resourceDirectory = Directory(
'${directory.path}${Platform.pathSeparator}offline',
);
if (resourceDirectory.existsSync()) {
resourceDirectory.deleteSync(recursive: true);
}
resourceDirectory.createSync(recursive: true);
final resourceDirectoryPath = resourceDirectory.path;
final vtpkFile = File(
'$resourceDirectoryPath${Platform.pathSeparator}basemap.vtpk',
);
// ベクター タイル レイヤーの URL をパラメーターに設定してタスクを作成・ロードします。
final vectorTilesExportTask =
ExportVectorTilesTask.withUri(_vectorTiledLayer!.uri!);
await vectorTilesExportTask.load();
// 現在のマップの表示範囲を取得します (この範囲のベクター タイルをダウンロードします)。
final downloadArea = _mapViewController.visibleArea!.extent;
// タスク実行時に使用するパラメーターを作成します。
final exportVectorTilesParameters =
await vectorTilesExportTask.createDefaultExportVectorTilesParameters(
areaOfInterest: downloadArea,
maxScale: _mapViewController.scale,
);
// Set parameters and download destination to create the task job (ExportVectorTilesJob).
_exportVectorTilesJob =
vectorTilesExportTask.exportVectorTilesWithItemResourceCache(
parameters: exportVectorTilesParameters,
vectorTileCacheUri: vtpkFile.uri,
itemResourceCacheUri: Uri.directory(resourceDirectoryPath),
);
11. Run ExportVectorTilesTask to start the download process. To run the task, execute the (ExportVectorTilesJob) run method from the job created by the task. Once the job succeeds, create a new offline map from the downloaded vector tiles.
try {
// Start the job and get the result.
final result = await _exportVectorTilesJob?.run();
// If the job succeeds, get the downloaded vector tiles and their style.
final vectorTilesCache = result?.vectorTileCache;
final itemResourceCache = result?.itemResourceCache;
if (vectorTilesCache == null || itemResourceCache == null) {
showErrorDialog('Vector tile cache or item resource cache is invalid');
return;
}
// Create a vector tile layer from the downloaded vector tiles.
final localVectorTileLayer = ArcGISVectorTiledLayer.withVectorTileCache(
vectorTilesCache,
itemResourceCache: itemResourceCache,
);
// Create an offline map from the basemap with the added vector tile layer.
_mapViewController.arcGISMap = ArcGISMap.withBasemap(
Basemap.withBaseLayer(
localVectorTileLayer,
),
);
} on ArcGISException catch (e) {
showErrorDialog(e.message);
} finally {
_exportVectorTilesJob = null;
}
12. Finally, hide the indicator.
// Hide the indicator.
setState(() {
_loading = false;
});
13. Next, create a function to display error messages called showErrorDialog .
void showErrorDialog(String message) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text('Info', style: Theme.of(context).textTheme.titleMedium),
content: Text(
'Failed to download vector tiles:\n$message',
),
actions: [
TextButton(
onPressed: () {
Navigator.of(context).pop();
},
child: const Text('Close'),
),
],
),
);
}
14. Create a function called takeOnline 'Online Display' button is tapped. To switch the currently displayed map from offline to online, set the variable ' _mapViewController.arcGISMap ' to ' _onlineMap '.
15. 今の状態では、_onlineMap が空のため、マップの初期作成時に _onlineMap に現在のマップを保存します。_onMapViewReady 関数の最後に下記を追加します。
void _onMapViewReady() {
・・・省略・・・
_mapViewController.arcGISMap = map;
// 追加
// オンラインマップへの切替用に、現在のマップ状態を保存します。
_onlineMap = map;
}
16. 以上でダウンロード機能の実装は完了です。ビルドして任意の場所をダウンロードしてみてください (現在のマップの表示範囲のデータがダウンロードされます) 。ダウンロードが完了すると、その範囲のデータしか表示されていないことが分かります。
まとめ
本記事では、オフラインマップの表示機能を実装しました。ExportVectorTilesTask や ExportVectorTilesJob では、マップを任意のスケールでダウンロードしたり、ダウンロード処理を途中で停止/再開したり、処理の進捗状況を確認したりといった設定も可能です。 また、今回実装したようにベースマップをインターネットを介してダウンロードする方法以外にも、事前にパッケージ ファイルを作成しておき、そのローカル データをアプリで表示することも可能です。利用可能なオフライン機能については、こちらのページもご覧ください。
関連リンク