2011年3月18日金曜日

GPSの経路ログ表示にGoogle Maps API V3 を使う (その4:吹出し(InfoWindow)の表示)

その1:KML 形式ファイルを作る に目次があります。

前回のその3:アイコン(Marker)の表示からの続きで,今回はアイコンに吹出し(Google Maps API では InfoWindow と呼ばれる)をつけ,アイコンがクリックされた際に1個ずつ表示されるようにする。Google Maps API の version 3 では,複数の InfoWindow が表示できるようになったため,いちいち消してから次を表示しないと画面が吹出しだらけになってしまう(らしい)。なので,今回はアイコンをクリックしたら,全部のInfoWindow を消してから最新のものを表示するようにしてみた。
追記その8:InfoWindowの改良で配列を使わない InfoWindow について書いた。

今回はまずは web ページの html ファイルを表示しよう。今回も,前回からの変更点を赤色の文字で表示する。

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
<head>
<meta name="viewport" content="initial-scale=1.0, user-scalable=no">
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<title>Route : EXAMPLE</title>
<link href="http://code.google.com/apis/maps/documentation/javascript/examples/default.css" 
rel="stylesheet" type="text/css">
<script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=false"></script>
<script type="text/javascript">
    var map;
    var wps = [
        ['経路1:出発点',35.0,136.5,1,'motorcycling',27,2,
        '<div id="wp_desc" style="font-size:12px; width:220px">
        <div id="wp_name">経路1:出発点</div><div id="wp_address">三重県のどっか</div> </div>'],
        ['経路1:Stay Point',35.0,136.55,2,
        'grocerystore',18,4,'<div id="wp_desc" style="font-size:12px; width:220px">
        <div id="wp_name">経路1:Stay Point</div><div id="wp_address">三重県のはず</div> </div>']
    ];
    var ctaLayer = new google.maps.KmlLayer('http://xxxx.yyyy.zzzz.jp/map_data.kml');
    var markersArray = [];
    var infowindowsArray = [];

// initialize function
    var centerLatLng = new google.maps.LatLng(35.0, 136.5);
    function initialize() {
        var myOptions = {
            zoom: 11,
            center: centerLatLng,
            mapTypeId: google.maps.MapTypeId.ROADMAP,
            scaleControl: true,
            scaleControlOptions: { position: google.maps.ControlPosition.BOTTOM_CENTER }
        }
        map = new google.maps.Map(document.getElementById("map_canvas"), myOptions);

        ctaLayer.setMap(map);
        setWPMarkers(map, wps);
    }
// --------------------------------------------------
    function setInfoWindow(marker,message) {
        deleteInfoWindow();
        var infowindow = new google.maps.InfoWindow({content: message});
        infowindow.open(map,marker);
        infowindowsArray.push(infowindow);
    }
    function attachMessage(marker, message) {
        google.maps.event.addListener(marker, 'click', function() {
            setInfoWindow(marker,message);
        });
    }
// Deletes all markers in the array by removing references to them
    function deleteInfoWindow() {
        if (infowindowsArray) {
            for (i in infowindowsArray) { infowindowsArray[i].close(); }
        }
    }
// --------------------------------------------------
    function setWPMarkers(map, locations) {
        for (var i = 0; i < locations.length; i++) {
            var points = locations[i];
            var icon_url = 'http://maps.gstatic.com/intl/ja_jp/mapfiles/ms/micons/'
                            +points[4]+'.png';
            var icon_shadow_url = 
                'http://maps.gstatic.com/intl/ja_jp/mapfiles/ms/micons/'+points[4]+'.shadow.png';
            if (points[4].match(/pushpin/i)) {
                icon_shadow_url = 
                    'http://maps.gstatic.com/intl/ja_jp/mapfiles/ms/micons/pushpin_shadow.png';
            }
            var image = new google.maps.MarkerImage(icon_url, new google.maps.Size(32,32),
            new google.maps.Point(0,0),  new google.maps.Point(points[5],(32-points[6])));
            var shadow = new google.maps.MarkerImage(icon_shadow_url,  new google.maps.Size(59,32),
            new google.maps.Point(0,0),  new google.maps.Point(points[5],(32-points[6])));
            var shape = { coord: [16, 0, 0, 32, 32, 32], type: 'poly' };
            var myLatLng = new google.maps.LatLng(points[1], points[2]);
            var marker = new google.maps.Marker({
                position: myLatLng,  map: map,  shadow: shadow,
                icon: image,  shape: shape,  title: points[0],  zIndex: points[3]
            });
            markersArray.push(marker);
            attachMessage(marker, points[7]);
        }
    }
// --------------------------------------------------
    function clearIcons() {
        if (markersArray) {
            for (i in markersArray) {
                markersArray[i].setMap(null);
            }
        }
    }
    function showIcons() {
        if (markersArray) {
            for (i in markersArray) {
                markersArray[i].setMap(map);
            }
        }
    }
// --------------------------------------------------
</script>
</head>

<body bgcolor="#D0D0D0" onload="initialize()">
<div>
<input style="color:blue" onclick="clearIcons();" type=button value="Icons 消去"> 
<input style="color:red" onclick="showIcons();" type=button value="Icons 表示"> 
</div>
<div id="map_canvas" style="width: 100%; height: 96.5%; position:absolute; top:21px; left:0px"></div>
</body>
</html>

今回は,global 変数として infowindowsArray を用意し,marker 変数を定義(同時にアイコン表示)した際に,attachMessage() 関数を呼び出している。その attachMessage() 関数の定義が中央付近に記述してある。また,deleteIcons() 関数は使っていないので,ここでは削除してある。

attachMessage(marker, message) 関数では,google.maps.event.addListener としてアイコンがクリックされた時のイベント処理として,setInfoWindow(marker,message) 関数を実行するように指定してある。ここで,setInfoWindow() 関数を呼び出す代わりに attachMessage() 関数の中に setInfoWindow() 関数の内容をそのまま書いてもいいのだが,そうすると確かどのアイコンをクリックしても最後の地点の情報を表示した(んだったと思う…。ごちゃごちゃやってて忘れちゃった)ので,こんなまどろっこしいことをしている。言い換えると,わざとサブルーチンにすることで,異なる InfoWindow の情報を異なるメモリに配置する,ってことだったと思う。サブルーチンに入れないと,ローカル変数っぽいやつでも同じ名前だと上書きされてしまうみたい。どうも JavaScript はメモリの管理がよくわからない…。でも,一つ言えるのは,もしローカル変数(のつもり)で用意したのに,振る舞いがおかしい時は,該当部分をわざとサブルーチンにするとうまくいく場合がある

追記)この振る舞いについて説明してくれているサイトを見つけた。連載:Ajax時代のJavaScriptプログラミング再入門ってところ。具体的には第3回 変数の宣言とスコープにローカル変数について書いてあった。やっぱりわかってる人は違うねぇ。原因はブロック・レベルのスコープは存在しないってことにあるらしい。つまり,関数(サブルーチン)の中で定義された変数はローカル変数だけど,for 文の中で定義してもローカル変数にはならない,ということみたい。一度この連載をちゃんと読まないとあかんなぁ。

setInfoWindow(marker,message) 関数では,まず deleteInfoWindow() 関数を呼び出して既に表示されている吹出しを消去している。こうしないと Google Maps API の version 3 ではアイコンをクリックするたびに吹出しが表示されて残るので,そのうち画面がアイコンでいっぱいになってしまう。消してから表示するようにすれば,いつも表示されるのは最後にクリックしたアイコンの吹出しだけとなる。

次に吹出し用の変数 infowindow を定義している。Google Maps API v3 での InfoWindow については,InfoWindowクラスに,使える関数メソッド)やクリックした際に発生するイベント指定できるオプションについての記述がある。オプションには吹出しに書くコメントを指定している(content: message の部分)。そして,クリックされたアイコンの吹出しを表示(open)している。最後に作成した infowindow 変数を global 変数として定義した配列にいれている。global 変数としては必ずしも配列を用意しなくてもいけると思うのだが,今回はこの作戦でうまくいったので,そのままにしている。
追記その8:InfoWindowの改良に配列を使わない作戦について書いた。

吹出しに書くコメントの内容は,スクリプトの始めの方に定義した配列 wps の中に各マーカーごとに書いてある。InfoWindow の中では,<div> の中に入れてサイズを(大きめに)指定してある。これはリンク付きの写真を載せた場合に,写真のクリック時に吹出しにスクロールバーが現れるのを防ぐためである。スクロールバーが現れるのは,どうやらリンクをクリックすると「非表示の項目」が表示されて,最初に指定された吹出しサイズだとはみ出してしまうから,ってのが理由みたいだけど,とにかくカッコ悪いので対処療法的に対処した。ここでは少し大きめに幅を決めている(Perl で自動的に計算させているので,変な数字になっている)。また,wp_address という id を持つ div の後にスペースがいれてあるが,こちらは縦方向で同じことが起こらない(起こっても見せたい内容が隠れない)ためのおまじない,である。

最後に,deleteInfoWindow() 関数では,InfoWindow 用の配列にある infowindow すべてを close している。

その5:センターマークの表示に続く)

0 件のコメント: