Google Maps API v3 で、InfoWindowをクラスタ化する方法


Google Maps APIはv3から、InfoWindowを画面上単一でなく、複数立てる事ができるようになっています。
なので、(こういう用途を想定してるオブジェクトではないので、このUIがよいかどうかは別問題として)マーカーではなくInfoWindowで地図上に情報を載せる要件が出てくる事があります。

こんな感じで。

こういう使い方をすると、マーカーと同じなので、密度のあるところではクラスタ化したいという要件も出てくるわけですが、マーカーであれば、js-marker-clusterを使えば簡単にクラスタ化できるものの、さて困った、InfoWindowはマーカーではないのでこれではクラスタ化できません。

何か方法はないか、できればjs-marker-clusterを使って、手も入れない形で、と考えていましたが、以下のような方法を思いつきました。

独自のマーカークラスを作成

独自のマーカークラスを作成します。継承関数については、こちらの記事を使わせていただきました。

独自のマーカークラス
function inherits(ctor, superCtor) {
    ctor.super_ = superCtor;
    ctor.prototype = Object.create(superCtor.prototype, {
        constructor: {
          value: ctor,
          enumerable: false,
          writable: true,
          configurable: true
        }
    });
};

function MyMarker(arg){
    MyMarker.super_.call(this,arg);
    this.visible = false;
    this.infoTarget = arg.infoTarget;
}
inherits(MyMarker, google.maps.Marker);
MyMarker.prototype.setMap = function(arg) {
    if (arg == null) {
        this.infoTarget.close();
    } else {
        this.infoTarget.open(arg);
    }
    MyMarker.super_.prototype.setMap.call(this, arg);
};

InfoWindowを独自マーカーの属性として与え、独自マーカーをクラスタに登録

作成した個々のInfoWindowを、直接地図に追加せず、独自マーカーの属性として登録し、独自マーカーをjs-marker-clusterに追加します。
独自マーカーの内部処理は、マーカーが地図に追加されると子のInfoWindowを開き、地図から削除されると子を閉じる動作をするよう記述していますので、これにより、マーカーがクラスタによって地図に追加されるとInfoWindowを開き、削除されると閉じる動作が実現されます。
独自マーカー自身は、内部でvisible=falseにしていますので、表示される事はありません。

独自マーカーをクラスタ登録
var cluster = new MarkerClusterer(map, []);
cluster.setMaxZoom(18);

for (var i=0;i < 100;i++) {
    var mll = new google.maps.LatLng(
        35  + (Math.random() * 20 - 10), 
        135 + (Math.random() * 20 - 10)
    );
    var mopts = {
        content: "" + i,
        position: mll, 
        disableAutoPan: true ,
        maxWidth: 240
    };

    var info = new google.maps.InfoWindow(mopts);

    var marker = new MyMarker({
        position: mll,
        infoTarget: info
    });

    cluster.addMarker(marker);
}

実際の動作例:こんな感じ

応用案

今回はクラスタで行いましたが、同じようなアプローチでOverlappingMarkerSpiderfierあたりでも使えるのではないかと思います。
また、今回はjs-marker-cluster側に手を入れないアプローチで行ったために、見えないマーカーという無駄オブジェクトを介していますが、MarkerクラスとInfoWindowクラスは、見せ方がsetMap(map)かopen(map)か、消し方がsetMap(null)かclose()か、あと位置取得メソッドの差がある程度で、実質同じ動作なので、js-marker-cluster側に手を入れれば、無駄なマーカーを介さず直接InfoWindowを操作させる事も可能と思います。
...というか、今書きながら思いついたけど、独自InfoWindowクラス作って、そいつにMarker等価のインタフェース加えてやって直接食わせる手もあるのか(うまくいくかは未確認)。

おまけ:InfoWindowの削除ボタンを消す

最初にも書いた通り、InfoWindowはマーカーと異なり、マーカーのような使われ方をする事を想定されてないので、「閉じる」ボタンがあります。
これで閉じられてしまうと、地図上から消えてしまいます(一旦クラスタされればまた出てくるけど)。
これを消す方法は今の所正式ルートではありませんが、いつ動かなくなっても泣かないアプローチであれば、今の現時点でのGoogleの実装に依存する形で、

InfoWindowの閉じるボタンを消す
.gm-style-iw + div {
  display: none;
}

で消す事ができます。

が、マーカーと違って場所を取る分、最大ズームでも重なってると下の情報は全く見えないし、消せないのは消せないのでそれも問題...とか考えだすと、そもそもこのUIがいいのかという最初の話に...