LoginSignup
6
2

More than 3 years have passed since last update.

[Maps SDK for Android] 地図にMarkerを置く(その2・Utility Library編)

Last updated at Posted at 2019-08-28

前に書いた記事で、Maps SDK for Android で表示した地図に配置する Marker について書きました。基本機能だけでも色々できますが、Google Maps Android API Utility Library を導入すると更にできることが広がります。
ここでは Utility Library の導入と使用法についてまとめてみました。

  1. 基本機能だけでできること(前の記事)
  2. Utility Library を使ってできること(この記事)

この記事でやること

  • Utility Library の導入
  • Marker をクラスタにまとめる
  • クラスタ化した Marker をカスタマイズする
  • Icon Generator を使って地図に地名を表示する

注意事項

Android Studio で Maps SDK for Android が使えることを前提にしています。そのための手順は以前まとめましたので、そちらご確認下さい。

環境

このページに書かれているコード等は以下の環境下で動作・検証しています。

macOS Mojave バージョン10.14.6
Android Studio 3.5
Pixel3a + Android 9

また、ここに書いた内容をもとにしたサンプルコードを GitHub で公開しています。

Utility Library の導入

モジュールの build.gradle に以下の dependencies を追加します。

dependencies {
    compile 'com.google.maps.android:android-maps-utils:0.5+'
}

Marker をクラスタにまとめる

地図のズームレベルが下がって複数の Marker が一箇所に集まったとき、「クラスタ」にまとまるようにすることができます。
下の図だと、左が全ての Marker がバラバラに表示されている状態、右がクラスタになって表示されている状態です。

util_1.png util_2.png

以下の手順で実装することができます。

  1. ClusterItem を実装したクラスを作成
  2. ClusterManager オブジェクトを生成
  3. ClusterItem を ClusterManager に追加

ClusterItem を実装したクラスを作成

Marker ひとつひとつに相当するクラスです。 com.google.maps.android.clustering.ClusterItem を実装して、必要なメソッドを定義すればOKです。
getSnippet / getTitle / getPosition では、それぞれ Marker の snippet / titie / position に相当するものを返すようにします。以下は実装例です。

class SegmentClusterItem(val segment: Segment) : ClusterItem {
    override fun getSnippet(): String = segment.flowerName
    override fun getTitle(): String = segment.title
    override fun getPosition(): LatLng = segment.coordinate
}

ClusterManager オブジェクトを生成

com.google.maps.android.clustering.ClusterManager クラスをインスタンス化します。
このとき GoogleMap の以下のメソッドに ClusterManager インスタンスを設定するようにします。

  • setOnCameraIdleListener
  • setOnMarkerClickListener

以下は ClusterManager をインスタンス化しているところです。

val manager = ClusterManager<SegmentClusterItem>(this@ClusteringActivity, this).apply {
    setOnCameraIdleListener(this)
    setOnMarkerClickListener(this)
}

ClusterItem を ClusterManager に追加

あとは ClusterManager#addItem で ClusterManager に ClusterItem を追加していくだけです。これで Marker がある程度まで接近するとクラスタにまとまるようになります。

Segment.values().forEach {
    manager.addItem(SegmentClusterItem(it))
}

クラスタにならない場合

上記の実装をしても Marker がクラスタにまとまらない場合は、Marker の数が少ないことが考えられます。
Marker がクラスタにまとまるかどうかは、一定の領域内に含まれる Marker(=ClusterItem)の数で判定されます。デフォルトでは、5個以上の Marker が領域内に含まれないとクラスタになってくれません。これを変更するには、クラスタにする Marker の数を定義した ClusterRenderer オブジェクトを ClusterManager#setRenderer にセットする必要があります。

以下は、DefaultClusterRenderer を継承した ClusterRenderer クラスの実装例です。shouldRenderAsCluster で「何個以上の Marker が集まったらクラスタにするか」を判定します。この例では、2つ以上でクラスタになるようにしています。

class SegmentClusterRenderer(context: Context, map: GoogleMap, manager: ClusterManager<SegmentClusterItem>) : DefaultClusterRenderer<SegmentClusterItem>(context, map, manager) {
    override fun shouldRenderAsCluster(cluster: Cluster<SegmentClusterItem>?): Boolean {
        // ClusterItemが一定距離内にいくつ集まったらクラスタ化するかをBooleanで返す
        return cluster?.size ?: 0 >= 2
    }
}

以下でこの Renderer を Manager にセットしています。

manager.renderer = SegmentClusterRenderer(this@ClusteringActivity, map, manager)

クラスタ化した Marker をカスタマイズする

ClusterRenderer と IconGenerator を併用することで、Marker のアイコンやクラスタのアイコンを変更することができます。
下の図は、大阪市の各区役所に Marker を立てて、区の花をアイコンで表示しています。クラスタになると、そこに含まれる Marker の数が女性のイラストに表示されるようにしています。
util_3.png util_4.png

ClusterRenderer で継承すべきメソッドは以下の通りです。
両方とも実装すべき処理は同じで、Marker やクラスタが描画されるときの MarkerOptions を変更することになります。

メソッド 実装内容
onBeforeClusterItemRendered Marker(=ClusterItem) のカスタマイズ
onBeforeClusterRendered クラスタのカスタマイズ

また com.google.maps.android.ui.IconGenerator を使えば任意のレイアウトで Bitmap を生成することが可能です。生成した Bitmap を、BitmapDescriptorFactory#fromBitmap を使って MarkerOptions#icon にセットすることで、Marker やクラスタのアイコンを任意に変更しています。
以下は DefaultClusterRenderer を継承した ClusterRenderer の例で、 res/layout/icon_segment.xml のレイアウトを使って Marker にアイコンを設定しています。icon_segment.xml には imageIcon というIDが付与された ImageView だけが配置されています。

private val itemImageView: ImageView
private val itemIconGenerator: IconGenerator = IconGenerator(context).apply {
    val iconView = LayoutInflater.from(context).inflate(R.layout.icon_segment, null, false).apply {
        itemImageView = findViewById(R.id.imageIcon)
    }
    setContentView(iconView)
}

override fun onBeforeClusterItemRendered(item: SegmentClusterItem, markerOptions: MarkerOptions) {
    itemImageView.setImageResource(item.segment.imageResId)
    val icon = itemIconGenerator.makeIcon()
    markerOptions.icon(BitmapDescriptorFactory.fromBitmap(icon))
}

ざっくり以下のような手順です。

  1. レイアウトから View をインフレート
  2. インフレートした View から、操作したい View を findViewById で取得
  3. IconGenerator#setContentView で、1の View をセット
  4. 2に任意の文字や画像をセット
  5. IconGenerator#makeIcon で Bitmap を生成
  6. MarkerOptions#icon に生成した Bitmap をセット

Icon Generator を使って地図に地名を表示する

地図上に地名などを表示したいという要件もあると思いますが、Marker に title や snippet をセットするだけでは Info Window のコンテンツとしてしか表示できません。
なのでここでは IconGenerator を使って、マーカーとして地名を強引に表示してみました。
util_5.png

IconGenerator で文字画像を作成するのは、前項と同じやり方で可能です。TextView を含んだレイアウト(もしくは TextView そのもの)を IconGenerator のコンテンツとして設定し、任意の文字列を TextView にセットしてから IconGenerator#makeIcon するという手順になります。
これで Bitmap ができますので、あとは Marker を生成して icon にセットするということになります。

val textView: TextView
val iconGenerator = IconGenerator(this@CustomIconActivity).apply {
    setBackground(null)    // nullにしないとデフォルトの背景が表示されてしまう(文字だけにならない)
    textView = layoutInflater.inflate(R.layout.icon_station_name, null, false) as TextView
    setContentView(textView)
}

textView.text = station.title

addMarker(MarkerOptions()
    .icon(BitmapDescriptorFactory.fromBitmap(iconGenerator.makeIcon()))
    .position(station.position)
    .anchor(0.5f, 0.0f)
    .zIndex(2.1f))

まとめ

以上、Utility Library を使った Marker のカスタマイズについてまとめてみました。
Utility Library には KML を扱ったりヒートマップを追加したりと、他にも多彩な機能が用意されています。
「参考」に公式ドキュメントへのリンクを貼っておきましたので、実際に読んで色々触ってみるのをお勧めします。この記事がそのきっかけになれば、大変嬉しく思います。

参考

Google Maps Android API Utility Library
googlemaps/android-maps-utils (GitHub)

6
2
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
2