目指せGISマスター #4 GoogleMap APIで道路を走るマーカーを作ってみた

ほとぼりが冷めてきたので(何の？)GoogleMapAPIについて備忘も込めて投稿する。
何かとOpenLayers2を使うことが多かったが、普及度的にもGoogleでいいんじゃね？ってなったので
「別にいっすよ。」くらいのノリでやってみたらハマった話。

どうでもいいが、Google Map APIを使おうと思ったらAndroid用やらなんやらに派生しまくっていて
「どうしたらいいの～～！」状態だったが、
「JavaScript API」でいいらしいことに気が付き、バイトリーダーの登場を待たずして解決した。


# Google Mapのマーカーはランドマーク的なもの？

Google Mapのマーカーと言えば赤い目玉の親父みたいなもののイメージが強い。
例えばGoogleで「皇居」などと検索すれば
皇居に赤い目玉の親父が立ったサムネイルが検索結果にでるはずだ。

ぐるなびではぐるなび的なアイコンに変わっていたりするが、
どうやらGoogle Mapにおける"マーカー"は地図上に「ここだよ」を示すものである。

https://developers.google.com/maps/documentation/javascript/markers?hl=ja

マニュアルによるとアニメーションやドラッグ＆ドロップなどは出来るようである。
当然、クリックも。
ランドマーク的な用途であれば十分だ。

# やりたかったのはマーカーを移動させること

しかも回転させながら。

GoogleMap上に車マーカーを走らせたかったのである。
車マーカーを道路沿いに走らせて、方向によってアイコンの向きも回転させたい。
この

- マーカーの移動
- マーカーの回転

これがGoogleMapのマーカーで実現できなかった。
(2017年6月時点。その後APIのアップデートがあったかはわかりません。)

走らせるだけなら表示して消して表示して消して・・・で移動させられることも無いが、
当然ちらつく。
回転は、上の方法で良ければ360度分画像を用意してアイコンを切り替えればできないこともない。
だがそんな無駄なことはやってられない。

カスタムマーカー、というものも用意されていたがやはり用途に合わない。
https://developers.google.com/maps/documentation/javascript/custom-markers?hl=ja


~~"マーカー"は地図上に「ここだよ」を示すものである。~~
"マーカー"は地図上に「ここだよ」を示すものでしかない。たぶん。


# カスタム オーバーレイなら自由だ

自由度を求めるならどうやらカスタムオーバーレイを使うしかない。
div要素を生成して表示できる。
これは自由だ。少なくとも回転は
transform:rotate()
をスタイルに指定すれば実現できるはずである。

後はマーカーの移動。
カスタムオーバーレイなら移動させるサンプルがある！
http://kakurenboman.blogspot.jp/2012/03/googlemapsoverlayview.html
http://www.mwsoft.jp/programming/googlemap/google_map_v3_custom_overlay.html

これで勝つる。
いろいろなサンプルのごった煮感あるが、↓のソースでいけた。
部分的だが記載する。

こんな感じで定義して

```js
function CustomMarker(latlng, map, args) {
    this.latlng = latlng;
    this.args = args;
    this.setMap(map);//mapはgooglemapのオブジェクト
}
```

表示する中身はこんな感じ。

```js
//markerクラスだと回転できないのでオーバーレイを使う
CustomMarker.prototype = new google.maps.OverlayView();
//表示内容はdivを作って表示する
CustomMarker.prototype.draw = function() {
    var self = this;
    var div = this.div;
    if (!div) {
        div = this.div = document.createElement('div');
        div.id ="test_div";//適当
        div.className = 'marker';//適当
        div.style.position = 'absolute';
        div.style.cursor = 'pointer';
        //アイコンに応じてサイズは変えること
        div.style.width = '36px';
        div.style.height = '24px';
        //定義時にargsでrotateを渡しdivのスタイルとして回転させる角度とする
        div.style.transform ='rotate('+this.args["rotate"]+'deg)';

        //アイコン自体は画像
        var img = document.createElement('img');
        //アイコンファイル指定
        img.src = 'car.png';//アイコンのファイル名
        //アイコンに応じてサイズは変えること
        img.width=36;
        img.height=24;
        div.appendChild(img);

        if (typeof(self.args.marker_id) !== 'undefined') {
            div.dataset.marker_id = self.args.marker_id;
        }
        //クリックイベントもとれる
        google.maps.event.addDomListener(div, "click", function(event) {            
            google.maps.event.trigger(self, "click");
        });
        var panes = this.getPanes();
        panes.overlayImage.appendChild(div);
    }
    var point = this.getProjection().fromLatLngToDivPixel(this.latlng);
    //アイコンの中央を指定した緯度経度にしたいのでオフセット値を付ける
    //アイコンの下の中央に合わせたければtopの指定は画像の縦サイズにするのが良いが、
    //アイコンを回転させる想定なので単純に中央にしている。じゃないとずれる。
    if (point) {
        div.style.left = point.x-18 + 'px';
        div.style.top = point.y-12 + 'px';
    }
};
```

イベント類はこんな感じ

```js
CustomMarker.prototype.remove = function() {
    if (this.div) {
        this.div.parentNode.removeChild(this.div);
        this.div = null;
    }
};

CustomMarker.prototype.getPosition = function() {
    return this.latlng;    
};

//移動はここ
CustomMarker.prototype.setPosition = function(myLatlng,rotate) {
  this.latlng = myLatlng;
  var point = this.getProjection().fromLatLngToDivPixel( this.latlng );
  this.latlng = myLatlng
  this.div.style.left = point.x-18 + 'px';//画像オフセット
  this.div.style.top = point.y-12 + 'px';//画像オフセット
  //スタイルを変えて回転させる
  this.div.style.transform ='rotate('+rotate+'deg)';
}
```

使い方。

```js://生成時
var overlay;
overlay = new CustomMarker(
    myLatlng, //google.maps.LatLngクラス
    map,
    {"rotate":角度}
);
//個人的にはオブジェクトを任意のidをキーに連想配列に入れておくと扱いやすいと思う
var overlays = new Object();
overlays[car_id] = overlay;//car_idは任意のid文字列

//移動時
overlays[car_id].setPosition(newLatlng,角度);//新しい緯度経度とマーカーの角度を指定
```

2点間の角度(と距離もある)は下記を参考に。
http://hamasyou.com/blog/2010/09/07/post-2/
次の点の緯度経度がわかっていれば現在地と次の点で角度を出した方が見た目は良い。
だが、次の点がわからない場合(ニアリアルタイムな表示の場合とか)は
前の点とを使って角度を出す。

<img width="350" alt="gmap_car.png" src="https://qiita-image-store.s3.amazonaws.com/0/121181/864601a5-0531-84eb-38fc-ee328d8a7203.png">

```
Map data ©2018 Google,ZENRIN
```

# ありがとうGoogle Map API

何となくマーカー使ったことあるし、これ動かせばいいんじゃね？
って思ってハマった感じがある。
でも、これでGoogle使えねー！と言ってはさすがにGoogleが不憫である。

流石天下のGoogleだけあって扱いやすさは抜群だなぁと思うのでありがとうございます。
少し面倒な描画もカスタムオーバレイで出来る、という抜け道も用意していただいて
ありがとうございます。
あとはマニュアル類のUIとかわかりやすさを・・・ね？

# 余談

最近、ドキュメント類はasciidocで書くようになったのだが、結構いい感じ。
でも慣れた後にマークダウンに戻るとやたら"+"を書きたくなる後遺症がつらい。
Qiitaも対応してくれないかなぁ。