はじめに
FlutterのMapLibreのAPIを調べてもよくわからなかったので、サンプルを読み解いて理解したいと思います。
注意
2022/11/06時点で、flutter_maplibre_glのサンプルのOffline Regionsでダウンロードすると、アプリがクラッシュします。
Example application is crashing when trying to download offline maps. #39で対応の状況が確認できるっぽい。
Mapbox GL JSで地理院地図Vector風の地図を表示するサンプルを参考に地理院の地図を使うようにしたところ、動作しました(が一部例外あり)
私はoffline_regions.dartの定義を以下のように変更して試しました。
final LatLngBounds hawaiiBounds = LatLngBounds(
southwest: const LatLng(17.26672, -161.14746),
northeast: const LatLng(23.76523, -153.74267),
);
final LatLngBounds santiagoBounds = LatLngBounds(
southwest: const LatLng(-33.5597, -70.49102),
northeast: const LatLng(-33.33282, -153.74267),
);
final LatLngBounds aucklandBounds = LatLngBounds(
southwest: const LatLng(-36.87838, 174.73205),
northeast: const LatLng(-36.82838, 174.79745),
);
final List<OfflineRegionDefinition> regionDefinitions = [
OfflineRegionDefinition(
bounds: hawaiiBounds,
minZoom: 3.0,
maxZoom: 8.0,
mapStyleUrl: "スタイルのURL",
),
OfflineRegionDefinition(
bounds: santiagoBounds,
minZoom: 10.0,
maxZoom: 16.0,
mapStyleUrl: "https://demotiles.maplibre.org/style.json",
),
OfflineRegionDefinition(
bounds: aucklandBounds,
minZoom: 13.0,
maxZoom: 16.0,
mapStyleUrl: "https://demotiles.maplibre.org/style.json",
),
];
final List<String> regionNames = ['Hawaii', 'Santiago', 'Auckland'];
tokyoだけはなぜか真っ黒な画面になったので、OfflineRegionDefinitionで指定した情報に問題があるのかもしれない。
(今後明らかにしたい)
関連ソース
flutter-maplibre-gl-main/example/lib 以下にある2つのファイルが対象です。
- offline_regions.dart
- offline_region_map.dart
offline_regions.dart
この画面には以下の処理があります
- 起動時にダウンロードされたリージョンの情報を読み込む。
- 読み込みは _updateListOfRegions()メソッドで処理される。
- マップアイコンでマップ(offline_region_map.dart)に移動。
- 移動は _goToMap()メソッドで処理される。
- ダウンロードアイコンで指定したリージョンの情報をダウンロード。その後佐削除アイコンに代わる。
- ダウンロードは_deleteRegion()メソッドで処理される
- 削除は _downloadRegion()メソッドで処理される
起動時にダウンロードされたリージョンの情報を読み込む _updateListOfRegions()メソッド
void _updateListOfRegions() async {
List<OfflineRegion> offlineRegions = await getListOfRegions();
List<OfflineRegionListItem> regionItems = [];
for (var item in allRegions) {
final offlineRegion = offlineRegions.firstWhereOrNull(
(offlineRegion) => offlineRegion.metadata['name'] == item.name);
if (offlineRegion != null) {
regionItems.add(item.copyWith(downloadedId: offlineRegion.id));
} else {
regionItems.add(item);
}
}
setState(() {
_items.clear();
_items.addAll(regionItems);
});
}
最初に、flutter_maplibre_glが提供するグローバルメソッドのgetListOfRegions()メソッドを呼び出しています。
これ、APIリファレンスを見ても全く説明がなく、flutter_mapbox_glのドキュメントを見ても同じように説明なし。
動作から察するに、getListOfRegions()メソッドはアプリケーションのキャッシュからデータを読み込んでいると思われる。
読み込んだデータをOfflineRegionListItemクラスに変換して、メンバ変数_itemsに格納している。
OfflineRegionListItemクラスは以下のように定義されている。
class OfflineRegionListItem {
OfflineRegionListItem({
required this.offlineRegionDefinition,
required this.downloadedId,
required this.isDownloading,
required this.name,
required this.estimatedTiles,
});
final OfflineRegionDefinition offlineRegionDefinition;
final int? downloadedId;
final bool isDownloading;
final String name;
final int estimatedTiles;
OfflineRegionListItem copyWith({
int? downloadedId,
bool? isDownloading,
}) =>
OfflineRegionListItem(
offlineRegionDefinition: offlineRegionDefinition,
name: name,
estimatedTiles: estimatedTiles,
downloadedId: downloadedId,
isDownloading: isDownloading ?? this.isDownloading,
);
bool get isDownloaded => downloadedId != null;
}
変数 offlineRegionDefinition として定義されている OfflineRegionDefinitionクラスは、APIリファレンスを確認すると以下の情報を保持している。
変数 | 型 | 備考 |
---|---|---|
bounds | LatLngBounds | このリージョンの範囲 |
mapStyleUrl | String | スタイルのURL |
maxZoom | double | ズームの最大 |
minZoom | double | ズームの最小 |
bounds、maxZoom、minZoomは重要。
これらの情報を元に、マップのタイルなどのデータをアプリケーションのキャッシュに保持すると思われる。
変数downloadedIdは_downloadRegion()メソッドでダウンロードしたリージョンの情報のID。
変数isDownloadingはリージョンの情報をダウンロードしたかどうかの管理フラグ
変数nameは表示名
変数estimatedTilesは推定されるタイルの数。実際の処理には影響はないと思われる。
指定されたOfflineRegionListItemを元にキャッシュを行う _downloadRegion()メソッド
void _downloadRegion(OfflineRegionListItem item, int index) async {
setState(() {
_items.removeAt(index);
_items.insert(index, item.copyWith(isDownloading: true));
});
try {
final downloadingRegion = await downloadOfflineRegion(
item.offlineRegionDefinition,
metadata: {
'name': regionNames[index],
},
);
setState(() {
_items.removeAt(index);
_items.insert(
index,
item.copyWith(
isDownloading: false,
downloadedId: downloadingRegion.id,
));
});
} on Exception catch (_) {
setState(() {
_items.removeAt(index);
_items.insert(
index,
item.copyWith(
isDownloading: false,
downloadedId: null,
));
});
return;
}
}
flutter_maplibre_glが提供するグローバルメソッドのdownloadOfflineRegion()メソッドを呼び出している。
このメソッドはAPIリファレンスを見ると、downloadOfflineRegion()の実装コードが掲載されている。
が、これみてもよくわかりません。
flutter_mapbox_glのドキュメントにはこのメソッドは載っていない・・・
なので、動作とコードから読み取るしかないようです。
見てみると、OfflineRegionクラスを返却します。
OfflineRegionクラスは以下のメンバ変数を持っています。
変数 | 型 | 備考 |
---|---|---|
definition | OfflineRegionDefinition | 引数で指定したOfflineRegionDefinitionオブジェクト(多分) |
id | int | ダウンロードしたデータのID |
metadata | Map | 引数で指定したメタデータ(多分) |
このメソッドでは、引数で指定したOfflineRegionDefinitionオブジェクトに基づいて、タイルなどの情報をキャッシュとしてダウンロードするようです。
そのキャッシュに紐づく情報がOfflineRegionオブジェクトとして返却されています。
キャッシュされたデータを削除する _deleteRegion()メソッド
void _deleteRegion(OfflineRegionListItem item, int index) async {
setState(() {
_items.removeAt(index);
_items.insert(index, item.copyWith(isDownloading: true));
});
await deleteOfflineRegion(
item.downloadedId!,
);
setState(() {
_items.removeAt(index);
_items.insert(
index,
item.copyWith(
isDownloading: false,
downloadedId: null,
));
});
}
このメソッドはシンプルですね。
OfflineRegionListItemオブジェクトの情報を初期化して、flutter_maplibre_glが提供するグローバルメソッドのdeleteOfflineRegion()メソッドを呼び出してキャッシュされたデータを削除しています。
offline_region_map.dart
このファイルは、offline_regions.dartで生成された画面で選ばれたマップを表示しているだけです。
_OfflineRegionMapStateクラスのbuildメソッドでMaplibreMapを生成する際に、指定された画面から渡されたOfflineRegionListItemオブジェクトの位置やズームの設定を指定しています。
ここでのポイントは、cameraTargetBoundsに範囲を指定している点です。
cameraTargetBoundsを指定することで、キャッシュされた範囲外を表示させないようにしています。
この辺はアプリの使用や目的にもよるかと思います。
まとめ
オフラインでmaplibreを使う場合、以下のような処理が必要になります。
- downloadOfflineRegion()メソッドでキャッシュする
- 画面起動時にgetListOfRegions()メソッドでキャッシュに関するOfflineRegionオブジェクトを取得する
- 描画はOfflineRegionオブジェクトの情報を元にMaplibreMapを生成して表示を行う
- 削除はdeleteOfflineRegion()メソッドで行う
さて、開発中のアプリで実装してみるぞ!