GoogleMapでカスタム可能なラベルを持つマーカー

# ラベル付きのマーカーをマップ上に表示したい

- マーカー`google.maps.Marker`はラベルを指定できる。
  - [MarkerLabel object specification](https://developers.google.com/maps/documentation/javascript/3.exp/reference?hl=ja#MarkerLabel)
  - オプションで指定できるのはフォントの色など簡易的。
    - 表示位置の変更もできないし、もっとカスタマイズしたラベルを表示したいところ。

```javascript:例
    var marker = new google.maps.Marker({
        position: new google.maps.LatLng({lat: latitude, lng: longitude}),
        map: map,
        label: {
            color: 'blue',
            fontFamily: 'sans-serif',
            fontSize: '10px',
            fontWeight: '100',
            text: 'Marker label text'
        }
    });
```

- マーカー上にテキストが表示されるが、細かい指定はできないのがツライ。

![image](https://qiita-image-store.s3.amazonaws.com/0/59517/0cefc80a-bf76-5218-0a53-54ea34fa3c14.png)


## ラベルの`<div>`を持つ独自のマーカークラスを作る

- `google.maps.MVCObject`をプロトタイプに持つ`LabeledMarker`を作成する。
  - 独自のタグをマップ上に表示するには`google.maps.OverlayView`を利用する。

```javascript:LabeledMarkerクラス
function LabeledMarker(map, markerOptions, labelOptions) {
    this.setMap(map);
    this.setPosition(markerOptions.position);

    // MarkerとLabel(Overlay)を作成する
    this.marker = this.createMarker(markerOptions);
    this.label = this.createLabel(labelOptions);
}
LabeledMarker.prototype = new google.maps.MVCObject();
LabeledMarker.prototype.setMap = function (map) {
    this.set('map', map);
};
LabeledMarker.prototype.setPosition = function (position) {
    this.set('position', position);
};
LabeledMarker.prototype.createMarker = function (markerOptions) {
    var marker = new google.maps.Marker(markerOptions);
    marker.parent = this;
    marker.bindTo('map', this);
    marker.bindTo('position', this);
    google.maps.event.addListener(marker, 'dragend', function () {
        google.maps.event.trigger(this.parent, 'dragend');
    });
    return marker;
};
LabeledMarker.prototype.createLabel = function (labelOptions) {
    var label = new google.maps.OverlayView();
    label.parent = this;
    label.bindTo('map', this);
    label.bindTo('position', this);
    label.onAdd = function () {
        var el = document.createElement('div');
        el.style.borderStyle = labelOptions.style.borderStyle || 'none';
        el.style.borderWidth = labelOptions.style.borderWidth || '0px';
        el.style.backgroundColor = labelOptions.style.backgroundColor || '';
        el.style.position = labelOptions.style.position || 'absolute';
        el.style.zIndex = labelOptions.style.zIndex || 100;
        el.innerHTML = labelOptions.innerHTML || '<div>' + labelOptions.text + '</div>';
        this.el = el;
        var panes = this.getPanes();
        panes.overlayLayer.appendChild(el);
    }
    label.draw = function () {
        if (!this.el) return;

        var overlayProjection = this.getProjection();
        var position = this.get('position');
        var xy = overlayProjection.fromLatLngToDivPixel(position);

        var el = this.el;
        el.style.left = xy.x + 'px';
        el.style.top = xy.y + 'px';
    };
    label.onRemove = function () {
        this.el.parentNode.removeChild(this.el);
        this.el = null;
    };
    label.position_changed = function () {
        this.draw(); // 親コンポーネントのポジションが変更されたら連動してラベルの表示位置を更新する
    };
    return label;
}
```

```javascript:mapにLabeledMarkerを表示する
var position = new google.maps.LatLng({
    lat: -34.397,
    lng: 150.644
});
var map = new google.maps.Map(document.getElementById('map-canvas'), {
    center: position,
    scrollwheel: false,
    zoom: 8
});

// ラベル付きマーカのオブジェクトを作成してマップに表示する
var labeldMarker = new LabeledMarker(map, {
    position: position,
    draggable: true
}, {
    text: 'Label text',
    style: {
        backgroundColor: 'white',
        borderStyle: 'solid',
        borderWidth: '1px'
    }
});
labeldMarker.addListener('position_changed', function () {
    // ドラッグによる位置変更をハンドリング
    console.log(JSON.stringify(this.get('position')));
});
```

## 表示サンプル
![image](https://qiita-image-store.s3.amazonaws.com/0/59517/6e046c64-5c69-f9cb-da92-0804aeb96fd3.png)