Flutter で地図アプリ開発に使える機能をシリーズで紹介するブログ記事です。この記事は住所検索編です。入力した住所の場所にマップを移動する、マップ上でタップした場所の住所を表示する 2 つの機能を実装していきます。この記事では、Flutter 用の地図 SDK (ArcGIS Maps SDK for Flutter) を使用して、それらの機能を実装する方法を紹介します。
サンプル コードは GitHub に公開しているので、すぐに確認したい方は、ダウンロードしてみてください。アプリの実行方法は README に記載しています。サンプルを実行するために、API キーの取得 (無償) が必要になります。
地図 SDK の導入からマップ表示までの方法は「Flutter で地図アプリを作成してみよう!」のブログ記事をご覧ください。この記事では、住所検索の機能にフォーカスして紹介します。
住所検索機能を使用するには、LocatorTask クラスを使用します。LocatorTask には、大きく分けて、以下の 2 つの機能が提供されています。
まずは、住所文字列から該当する場所を検索するジオコーディング機能を実装していきます。
1. 「Flutter で地図アプリを作成してみよう!」のブログ記事の手順 6 で、マップビュー コントローラーに作成したマップを設定してマップの表示までできたら、_MyHomePageState クラスの最初で、LocatorTask.withUri コンストラクターを使用して LocatorTask を作成します。
class _MyHomePageState extends State<MyHomePage> {
// 追加
//ジオコードサービスの URL を指定して、ジオコーディング用のタスクを作成します。
final _locatorTask = LocatorTask.withUri(
Uri.parse(
'https://geocode-api.arcgis.com/arcgis/rest/services/World/GeocodeServer',
),
);
・・・
ジオコードサービスには、ArcGIS Location Platform のサービスを使用します。住所検索(ジオコーディング/リバースジオコーディング)は、毎月 20,000 回まで無償で利用できます。
2. 続いて、ジオコーディングに使用する住所を入力するためのテキスト入力フォームを作成します。テキスト入力フォームには、Flutter の TextField ウィジェットを使用します。_MyHomePageState クラスの最初で、TextField ウィジェットに設定する _textEditingController と _searchFocusNode 変数を作成します。TextEditingController は、初期状態で表示されるテキストの設定と現在のテキストの取得に使用し、FocusNode はウィジェットのフォーカス状態を管理するのに使用します。
class _MyHomePageState extends State<MyHomePage> {
// 追加
final _textEditingController = TextEditingController(text: '東京都千代田区平河町2-7-1');
final _searchFocusNode = FocusNode();
・・・
3. 続いて Widget build() の Column ウィジェット内にある ArcGISMapView ウィジェットの前に TextField ウィジェットを追加します。
mainAxisAlignment: MainAxisAlignment.center,
children: [
//追加
TextField(
focusNode: _searchFocusNode,
controller: _textEditingController,
decoration: InputDecoration(
hintText: '住所を入力...',
prefixIcon: const Icon(Icons.search),
suffixIcon: IconButton(
onPressed: dismissSearch,
icon: const Icon(Icons.clear),
),
),
onSubmitted: onSearchSubmitted,
),
Expanded(
// ウィジェット ツリーにマップビューを追加し、コントローラーを設定します。
child: ArcGISMapView(
・・・
4. TextField のフォームで住所の入力が完了した時に実行される onSearchSubmitted メソッド(TextField の onSubmitted プロパティに設定)を作成します。onSearchSubmitted メソッドでは、LocatorTask を使用してジオコーディングを行い、その結果の座標にマーカーを表示する処理を実装しています。
void onSearchSubmitted(String value) async {
// ジオコーディング(文字列から座標を取得)用の検索パラメーターを作成します。
final geocodeParameters = GeocodeParameters()
..outputSpatialReference = _mapViewController.spatialReference;
// TextField に入力された文字列をもとにジオコーディングを実行して結果を取得します。
final geocodeResult = await _locatorTask.geocode(
searchText: value, parameters: geocodeParameters);
if (geocodeResult.isEmpty) return;
final firstResult = geocodeResult.first;
// 結果の座標にアイコンを表示して、中心になるようにマップを移動します。
final pictureMarkerSymbol = PictureMarkerSymbol.withUri(Uri.parse(
"https://static.arcgis.com/images/Symbols/Shapes/BlueStarLargeB.png"));
pictureMarkerSymbol.height = 50;
pictureMarkerSymbol.width = 50;
final graphic = Graphic(
geometry: firstResult.displayLocation, symbol: pictureMarkerSymbol);
_mapViewController.graphicsOverlays[0].graphics.add(graphic);
_mapViewController.setViewpointCenter(firstResult.displayLocation!);
}
5. 続いて、テキスト入力フォームの右横にある [X] アイコンをタップしたときに実行される dismissSearch メソッド(IconButton の onPressed プロパティに設定)を作成します。dismissSearch メソッドでは、テキスト入力フォームのテキストをクリアして、キーボードを閉じる処理を実装しています。
void dismissSearch() {
// テキスト フィールドをクリアして、キーボードを閉じます。
setState(() => _textEditingController.clear());
FocusManager.instance.primaryFocus?.unfocus();
}
6. これでジオコーディング機能を実装できたので、この状態でビルドして、機能を試してみます。住所を入力し、キーボードの完了ボタン等をタップして住所の入力を完了すると、マップ上の該当する場所にアイコンが表示されます。
7. 続いて、リバースジオコーディングの機能を実装していきます。既に作成済みの ArcGISMapView ウィジェットに、マップをタップした時に呼び出される onTap プロパティを設定します。
child: ArcGISMapView(
controllerProvider: () => _mapViewController,
onMapViewReady: _onMapViewReady,
//追加
onTap: onSearchTap,
),
8. マップ上をタップした時に実行される onSearchTap メソッドを設定します。onSearchTap メソッドでは、LocatorTask を使用してリバースジオコーディングを行い、その結果の住所テキストを表示する処理を実装しています。
void onSearchTap(Offset localPosition) async {
// 既存のグラフィックスを削除します。
final graphicsOverlay = _mapViewController.graphicsOverlays[0];
if (graphicsOverlay.graphics.isNotEmpty) graphicsOverlay.graphics.clear();
// スクリーン ポイントをマップ ポイントに変換します。
final mapTapPoint =
_mapViewController.screenToLocation(screen: localPosition);
if (mapTapPoint == null) return;
// タップした場所を示すグラフィックス オブジェクトを作成します。
graphicsOverlay.graphics.add(Graphic(geometry: mapTapPoint));
// リバースジオコーディング(座標から住所を取得)用の検索パラメーターを作成します。
final reverseGeocodeParameters = ReverseGeocodeParameters()..maxResults = 1;
// タップした場所とパラメーターを使用してリバースジオコーディングを実行します。
final reverseGeocodeResult = await _locatorTask.reverseGeocode(
location: mapTapPoint,
parameters: reverseGeocodeParameters,
);
if (reverseGeocodeResult.isEmpty) return;
// 結果から住所文字列を取得してダイアログに表示します。
final firstResult = reverseGeocodeResult.first;
final addressString = firstResult.label;
if (mounted) {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(content: Text(addressString));
},
);
}
}
9. これでリバースジオコーディング機能も実装できたので、ビルドして機能を試してみます。マップ上の任意の場所をタップすると、その場所の住所テキストがマップ上に表示されます。
本記事では、LocatorTask クラスを使用してジオコーディングとリバースジオコーディングの機能を実装しました。この記事では紹介しきれませんでしたが、LocatorTask クラスには、検索時に使用できる便利なパラメーターがいくつか用意されています。ジオコーディングでは、検索対象とする地理的な範囲や、あいまい検索を許可するように、検索文字列と実際の住所文字列とのマッチ率の閾値等も指定できます。リバースジオコーディングでは、返す住所の表示言語も設定できます。下の画像は、同じ住所を日本語と英語で表示していますが、ユーザーが選択した言語で住所の表示言語を切り替えるような機能も実装できます。