【flutter】flutter_mapを使いこなす！

# はじめに
この記事は「NTTテクノクロス Advent Calendar 2024」の5日目の記事です。

こんにちは！NTTテクノクロスの沼田です。
現在、flutterで地図アプリの開発をしています。

今回は、実際に案件で使用しているライブラリや、苦労している点を踏まえ、flutter_mapをご紹介いたします。

# flutter_map とは
・Flutterで地図を表示するためのライブラリ
・オープンソースのMAP-API（OSMなど）を使うのであれば基本無料で作成可能
・カスタマイズが豊富

https://docs.fleaflet.dev/


# こんな人におすすめ
・費用を抑えたい
・細かくカスタマイズしたい（詳細は後述）
・独自の地図データを使用したアプリを作成したい（詳細は後述）

# flutter_mapで何ができるのか
地図の基本的な機能はもちろん、拡張も豊富なので、大抵のことは出来ます。
（GoogleMapのようなアプリが作成できます）

今回は、細かいカスタマイズ方法までご紹介します。

###  ◾️ 地図の表示
オンライン地図、オフライン地図どちらの表示も可能。任意の地図タイル画像の表示も可能です。

例 ）オフライン地図表示の例
　　asset配下に置いた地図タイル画像を使用している公式サンプル
　　[bundled_offline_map.dart](https://github.com/fleaflet/flutter_map/blob/1150d3bfaa7ee946340f86e9d81ff544a121db8f/example/lib/pages/bundled_offline_map.dart)

###  ◾️ 地図操作の制御
回転不可、操作可能範囲を設定といった基本的な制御は、 [InteractionOptions](https://pub.dev/documentation/flutter_map/latest/flutter_map/InteractionOptions-class.html)を使用します。

自分好みに細かい制御をカスタマイズする場合は、 [InteractionOptions](https://pub.dev/documentation/flutter_map/latest/flutter_map/InteractionOptions-class.html)を使用します

**★ GoogleMapのように「拡大縮小時のみ回転なし」にしたい**
以下設定で適用できます。
```dart
interactionOptions: const InteractionOptions(
 rotationWinGestures:
 	 MultiFingerGesture.pinchZoom | MultiFingerGesture.rotate,
 ),
```

###  ◾️ ポリゴンやラインの表示
[Polygon Layer](https://docs.fleaflet.dev/layers/polygon-layer)を使用
自由にポリゴン表示ができます。

[Polyline Layer](https://docs.fleaflet.dev/layers/polyline-layer)を使用
画像はプロパティで点線にしています。点線の感覚をもう少し広げたかったのですが、現在間隔を設定できるプロパティは用意されていませんでした。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/654246/fd0f0c94-ee99-e95c-96cf-ffb9e4810f42.png)

###  ◾️ 地図マーカーの表示
[Marker Layer](https://docs.fleaflet.dev/layers/marker-layer)を使用
画像やButtonをマーカーに設定できます。
画像のマーカーは、マーカーのアイコンと、テキストをColumnで並べて表示しています。


**★ 指定した箇所を中心にマーカーを回転させたい場合**
例えば、地図に以下の画像のようなマーカーを表示し、地図を回転させたとします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/654246/33a5286d-7362-f304-1278-39f18a5b2f84.png)
デフォルトの設定だと、画像の中央を回転軸としてマーカーも回転するので、本来回って欲しい座標軸でマーカーが回転されず、マーカーの表示がずれてしまいます。
ピンの先っぽを回転軸としたい場合、alignmentのbottomCenterなどを使って好みの場所に座標軸を設定することで解消できます。

# 別ライブラリとの組み合わせで出来ること
###  ◾️ 現在地・方向の表示
[flutter_map_location_marker](https://pub.dev/packages/flutter_map_location_marker) パッケージを使用
画像のように現在地を表示することが出来ます。現在地アイコンは自由にカスタマイズ可能です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/654246/803b0fbc-b84f-759c-9c36-bed683f97a45.png)







デフォルトでは位置情報を扱うGeolocatorパッケージと、端末の向きを取得するflutter_compassパッケージが使用されており、それらをもとに現在地が表示されています。

###  ◾️ ヘディングアップ・ノースアップ
同じく、[flutter_map_location_marker](https://pub.dev/packages/flutter_map_location_marker) パッケージを使用
GoogleMapの右下にある現在地中心ボタンと同様の実装が可能で、端末の向きに従い、地図も回転させるといった制御が可能です。




###  ◾️ アニメーションの追加
[flutter_map_animations](https://pub.dev/packages/flutter_map_animations)パッケージを使用

flutter_mapのデフォルトのアニメーションは、カクカクした動きしか対応していません。本パッケージによって、滑らかなアニメーションをつけることが可能です

###  ◾️ マーカーピンのクラスタリング
[flutter_map_marker_cluster](https://pub.dev/packages/flutter_map_marker_cluster)パッケージを使用
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/654246/21aa9e0c-cafe-898f-e06d-2c16b6516b7b.png)

# 【iOSのみ】 flutter_map_location_markerについて
現在地の表示を行っているflutter_map_location_markerですが、
同じ画面でGeolocatorパッケージを使用して現在地の更新(getPositionStream）、取得(getCurrentPosition）を行うと、位置情報が更新されなかったり、現在地のちらつきが発生するといった事象がありました。

結果、Geolocatorではなく、Locationパッケージを使用するよう変更したことで対処しましたが、現在地を表示する[CurrentLocationLayer](https://pub.dev/documentation/flutter_map_location_marker/latest/flutter_map_location_marker/CurrentLocationLayer-class.html)が正常に描画されるかは、確認を行うのがいいと思いました。
記録用に、最終的にうまく描画できた時のCurrentLocationLayer

```dart
CurrentLocationLayer(
    positionStream: const LocationMarkerDataStreamFactory()
    .fromGeolocatorPositionStream(),
    .asBroadcastStream(),
    ....
)    
```

# iOSは位置情報に注意
（flutter_mapとは直接関係ない話になります）

flutterで位置情報を扱うパッケージ（Geolocator、Locationなど）を使用する際は、Androidで問題なく動いても、iOS実機での検証は必須です。

###  連続で現在地を取得しない
連続で現在地取得を行うと、２回目以降取得できなくなることが多くあります。
こちらはissueに上がっています。（Geolocator）
https://github.com/Baseflow/flutter-geolocator/issues/1193

###  対策
**１、位置情報の取得方法を変更する**
getPositionStreamを使用して位置の更新を取得する実装に変更することで、頻繁にCurrentLocationを実行しないようにする。


**２、最後に取得された既知の場所を取得するように変更する**
getLastKnownPositionを使用して最後に取得した位置情報を返す実装に変更する。
インターバルを設けて、指定秒数以内に取得できない場合はgetLastKnownPositionを使う、といった実装になるかと思います。

開発中のアプリでは、信憑性の高い現在地が必要だったので１を採用しました。


# 最後に
ざっとflutter_mapの紹介をしましたが、やろうと思えばGoogleMapと似たようなアプリの作成は可能です。

無料で簡単に使えるので、お試しで使いたい方は是非公式を参考にしてみてください。

繰り返しにはなりますが、位置情報周りの実装はAndroidとiOSそれぞれの実機で、**こまめに**、検証を行いながら開発することをおすすめします。