アカベコマイリ

HEAR NOTHING SEE NOTHING SAY NOTHING

WPF で Google Map その 3

WPF で Google Maps API を利用するシリーズその 3。今回はマップ内で発生したイベントをハンドリングして、マップの中央座標をステータスバーに即時反映してみる。

サンプル プログラム

前回のサンプルからの変更点は以下。

  • 起動時に表示しているマップの座標を表示
  • マップの移動が発生した時、中央の座標をリアルタイムに表示
  • これらの変更に伴い、現在位置ボタンを廃止
  • ローカル HTML から CSS と JavaScript も分離
  • ボタンの Click 処理を Command 化

サンプル プログラムのプロジェクト一式は以下。

ローカル HTML のファイル分割

HTML 内に CSS や JavaScript が直に定義されているとメンテナンスしにくい。また CDATA セクションなどの記述もかったるいので、これらを分離する。CSS は style.css、JavaScript は map.js と名付け、HTML を以下のように変更した。

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<!-- saved from url=(0017)http://localhost/ -->
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <meta http-equiv="content-type" content="text/html; charset=utf-8"/>
    <title>Map</title>
    <link rel="stylesheet" type="text/css" href="style.css" />
    <script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=false"></script>
    <script type="text/javascript" src="map.js"></script>
</head>
<body onload="initialize()" scroll="no">
    <div id="map_canvas"></div>
</body>
</html>

これまでのサンプルに比べると非常にスッキリ。JavaScript の初期化は initialize 関数にまとめたので、body タグの onload で呼び出すようにした。

body タグの scroll 属性に no を設定しているのは、マップ内のスクロールバー表示を抑止する為である。WPF の WebBrowser にもスクロールバー設定用のプロパティがあるが、これは Control クラスから継承されたもので、コントロール自体のスクロールバー用である。

ページ内のスクロールバー制御は HTML 側の世界となるので、そちらで設定する必要がある。

Google Map API のイベント

Google Map API は、UI やマップの操作が行われた時、そのタイミングを補足する為のイベント機能をサポートしている。

イベントの詳しい内容については Google Maps API V3 Reference を参照のこと。JavaScript の実装は以下のようになる。

var map; //! マップ オブジェクト。
var geo; //! Geo コード取得用オブジェクト。
var isInitialized = false; //! 初期化フラグ。

/**
 * マップの初期化を行います。
 */
function initialize()
{
    var mapdiv    = document.getElementById( "map_canvas" );
    var myOptions = { zoom: 16, center: new google.maps.LatLng( 35.6894876, 139.6917064 ), mapTypeId: google.maps.MapTypeId.ROADMAP, scaleControl: true };
    map           = new google.maps.Map( mapdiv, myOptions );
    geo           = new google.maps.Geocoder();

    // マップの中央位置が更新された時のイベント
    google.maps.event.addListener( map, 'center_changed', function()
    {
        setTimeout( getLocation, 200 );
    });

    // マップのタイル読み込みが完了した時のイベント
    google.maps.event.addListener( map, 'tilesloaded', function()
    {
        // 起動時に一度だけ座標を更新する
        if( !isInitialized )
        {
            isInitialized = true;
            getLocation();
        }
    });
}

/**
 * 現在位置の緯度・経度を取得してマネージ コードへ設定します。
 */
function getLocation()
{
    window.external.Location = "緯度 : " + map.get_center().lat() + "、経度 : " + map.get_center().lng();
}

/**
 * 指定された住所の座標へ、マップを移動します。
 *
 * @param[in] address 住所。
 */
function moveMap( address )
{
    if( geo )
    {
        geo.geocode( { 'address': address }, function( results, status )
        {
            if( results && results[ 0 ] )
            {
                map.setCenter( results[ 0 ].geometry.location );
                getLocation();
            }
        });
    }
}

今回はマップ移動と連動して座標を更新するため、中央位置が更新された時に発生する center_changed をハンドリングしている。このイベントはマップでドラッグ移動を行うと大量に発生する。よってメイン スレッドの応答性を損ねないように 200 ミリ秒の遅延をはさんで処理している。

また起動時に一度だけ座標を更新するため、マップのタイル読み込みが終了したときにフラグ判定して初期化を実行。現在の実装では initialize 関数の時点でマップの状態や WPF のコントロール インスタンス生成が完了していない可能性もある。そのため処理は tilesloaded まで遅延させている。

プログラムの実行

プログラムを実行すると、以下のようになる。

スクリーン ショット

静止画だとわかりにくいけれど、マップ内でドラッグ移動を行うとステータスバーの座標情報が即時更新 (正確には 200 ミリ秒の遅延あり) されてゆく様子を確認できるはず。