今頃になって Google Maps API v2 から v3 対応へ修正

年末まで仕事が埋まらず、暇になってしまいました。

暇な時間の有効活用のために、まずは長い間 Google Maps API バージョン 2 のまま手つかずだった本館の Google Maps APIRSS 1.0 から地図表示するページをバージョン 3 に対応させることにしました。平日の朝っぱらか、いい年した大人が何をやっているのだろうと我ながら苦笑しつつも、このエントリの作文まで済ませてアップロード。さて、せっかく山下公園イチョウが黄色になりかけているので、これからデジカメもって日没前に撮ってくることにします。

今回は v3 になってなくなった機能を補完するために、jQuery を使ってみました。Mac OS Xsafari、同版と ubunutu linuxfirefox 3.5、ubuntuepiphany で試してみたところ、これらでは問題なく動作しているようです。MSIE を初め windows 環境ではチェックしていません。どなたか動作検証していだたけたらありがたいのでリンクを貼っておきます。

tkbure.html HTML ページ
geotkbure-06.js JS ソース

HTML のヘッダ要素の中でスクリプトをロードする箇所は次のように変わりました。APIキー指定がいらなくなり、クエリ・パラメータに sensor と region を指定します。sensor は GPS 等のインターフェースをもたないユーザエージェント用は false。region を JP に指定すると、JP 決め打ちのマップになりますが、試してみたらユーザ・エージェントのリクエスト・ヘッダをデフォルトのネゴシエーションに使っているようなので、私は region 指定をしないことにしました。マップ描画域サイズをこのページでは固定にしていますので、viewport メタ要素で user-scalable=no にしてます。

  +<script src="../jquery-1.4.3.js" type="text/javascript"></script>
  +<script src="geotkbure-06.js" type="text/javascript"></script>
  +<meta name="viewport" content="initial-scale=1.0, user-scalable=no" />
  +<script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=false"></script>
  -<script src="geotkbure-05.js" type="text/javascript"></script>
  -<script src="http://maps.google.com/maps?file=api&amp;v=2&amp;key=..." type="text/javascript"></script>

このページでは、マップを表示する div 要素と、RSS の item 一覧を表示する ul 要素を id で指定するようにしています。ここは従来通りです。

   <div id="map" style="width: 532px; height: 512px">
   <ul id="tkburelist"><li>&nbsp;</li></ul>

マップ初期化コードの実行は jQuery にまかせています。主な変更点は、以下3つです。Gナンタラがgoogle.maps.ナンタラになり、map インスタンスの作り方が簡単になりました。イベント・リスナに渡る実引数が変更されました。Google Maps API と関係ありませんが、v2 用は以前 prototype.js を混ぜて使っていた名残りで DOM 要素を $('id名') 関数で取り出していた箇所すべてを、jQuery('セレクタ記述')に書き換えています。

  +var map=new google.maps.Map(jQuery('#map').get(0), {
  +  scaleControl: true,
  +  zoom: 17-3,
  +  center: init_latlng,
  +  mapTypeId: google.maps.MapTypeId.SATELLITE
  +});
  -var map=new GMap2($('map'));
  -map.setCenter(init_latlng, 17-3, G_SATELLITE_MAP);
  -map.addControl(new GLargeMapControl());
  -map.addControl(new GMapTypeControl());
  -map.addControl(new GScaleControl());
  +google.maps.event.addListener(map, 'click', function(event){
  +  var point = event.latLng;
  +  jQuery('#gp').attr('value', to_geo_param(point));
  +  jQuery('#et').attr('value', toExiftool(point));
  +});
  -GEvent.addListener(map,'click',function(overlay,point) {
  -  if (point) {
  -    $('gp').value=to_geo_param(point);
  -    $('et').value=toExiftool(point);
  -  }
  -});

XmlHttpRequestRSS を読み取るのに v2 用では Google Maps API の GDownloadUrl() を使っていましたが、v3 では廃止されたので、jQuery.ajax を使って書き直しています。parseResponse() に渡す DOM のドキュメント・ノードを jQuery.ajax では自動的に success コールバックへ渡してくれますが、Google Maps API v2 では、GXml で変換してから documemtElement プロパティを取り出さなければいけませんでした。

   GeoItems = function(a,b) {
     if (!a) throw 'require a google map object';
  +  this.ctx={map:a, cur_infowin:null};
  -  this.map=a;
  +  this.param_latlng=null;
  -  this.autoCentering = false;
     this.list=b;
  -  this.listObservers = [];
     this.items=[];
   };

  +getItems: function(a,b) {
  -GeoItems.prototype.getItems = function(url,parser,c) {
  -  if (!parser) throw 'require a parser';
  +  this.param_latlng=b;
  -  self.autoCentering=c;
  -  var self = this;
  +  jQuery.ajax({
  +    url: a,
  +    dataType: 'xml',
  +    success: jQuery.proxy(this.buildItems, this)
  +  });
  -  GDownloadUrl(url,function(data,responseCode){
  -      var xml = GXml.parse(data);
  -      self.items = parser.parseResponse(xml.documentElement);
  -      self.redraw();
  -  });
   },
  +buildItems: function(data) {
  +  this.items = (new TkbureRss).parseResponse(data);
  +  this.redraw();
  +},

Marker 等のマップのパーツ類は、v3 で使い方が変わった箇所です。v2 では、Marker クリックで InfoWindow を開くと、他の Marker をクリックする時点で、前に開いている InfoWindow を閉じる動作をしていましが、v3 では開きっぱなしになります。v3 の挙動でもいいかと思ったのですけど、ここでは v2 に合わせて一度に1つしか InfoWindow を開かないようにしたので、その分の記述が膨らんでいます。なお、v3 用では、Marker インスタンスを this.items[i].marker に記録するように仕様を変更しました。

  +createMarker: function(i) {
  -GeoItems.prototype.createMarker = function(i) {
  +  var ctx = this.ctx;
     var item = this.items[i];
  +  var pt = new google.maps.LatLng(parseFloat(item.y),parseFloat(item.x));
  +  var marker = new google.maps.Marker({
  +    position: pt,
  +    map: ctx.map,
  +  });
  -  var marker = new GMarker(new GLatLng(parseFloat(item.y),parseFloat(item.x)));
     item.marker = marker;
  +  var infowin = new google.maps.InfoWindow({
  +    content: item.infoHtml
  +  });
  +  google.maps.event.addListener(marker, 'click', function(){
  +    if (ctx.cur_infowin) {
  +      ctx.cur_infowin.close();
  +    }
  +    infowin.open(ctx.map, marker);
  +    ctx.cur_infowin = infowin;
  +  });
  -  var str = item.infoHtml;
  -  GEvent.addListener(marker,'click',function(){
  -    marker.openInfoWindowHtml(str);
  -  });
  -  this.map.addOverlay(marker);
  +  google.maps.event.addListener(infowin, 'closeclick', function(){
  +    ctx.cur_infowin = null;
  +  });
     item = null;
  +  pt = null;
  }

マップから Marker の取り除くとき、v3 では map インスタンスを使わなくて良くなりました。

     if (this.items[i].marker) {
  +    google.maps.event.clearListeners(this.items[i].marker,'click');
  -    GEvent.clearListeners(this.items[i].marker,'click');
  +    this.items[i].marker.setMap(null);
  -    this.map.removeOverlay(this.items[i].marker);
       this.items[i].marker = null;
     }

RSS のタイトル・リストをクリックして、map に InfoWindow を表示させる部分は、jQuery を使って書き直しました。Marker の LatLng インスタンスを取り出すメソッド名が変更されています。

  +createList: function(i) {
  -GeoItems.prototype.createList = function(i) {
  +  var li = jQuery('<li></li>');
  -  var li = document.createElement('li');
  +  jQuery(this.list).append(li);
  -  $(this.list).appendChild(li);
  +  var ctx = this.ctx;
  -  var map = this.map;
     var marker = this.items[i].marker;
  +  var a = jQuery('<a></a>',{href:'javascript:void(0)',html:this.items[i].listHtml});
  -  var a = document.createElement('a');
  -  a.href = 'javascript:void(0)';
  -  a.innerHTML = this.items[i].listHtml;
  +  a.click(function(){
  -  var observer = function(){
  +    ctx.map.setCenter(marker.getPosition());
  -    map.setCenter(marker.getPoint());
  +    google.maps.event.trigger(marker,'click');
  -    GEvent.trigger(marker,'click');
  +  });
  -  };
  -  this.listObservers.push(observer);
  -  GEvent.addDomListener(a,'click',observer);
  +  li.append(a);
  -  li.appendChild(a);
     a = null;
     li = null;
  -  observer = null;
   }

  -GeoItems.prototype.removeLists = function() {
  -//途中略
  -}

v2 用では自力でやっていたイベント・リスナの開放は、v3 用では jQuery にまかせています。

     if (!this.list) return;
  +  jQuery(this.list).empty();
  -  if (this.listObservers.length>0) {
  -    for (var i = 0; i<this.listObservers.length; i++) {
  -      GEvent.removeListener(listObservers[i]);
  -      this.listObservers[i] = null;
  -    }
  -  }
  -  $(this.list).innerHTML = '';
  -  this.listObservers = [];

v3 対応になったのは良いのですが、ストリートビューと画像をワンクリックで対比できるようになってしまいました。チェックしてみると数10メートル単位でずれていることが丸分りになっていたりしました。かといって、いちいち EXIF タグの付け替えをして精度を調整する気にはならず、そんなものだと諦めてしまうことにしましょう。

(2010-11-18修正)ソースリストを `diff --unified --ignore-space-change` の出力風に変更しました。
(2010-11-24修正) getItems(a,b) を jQuery.proxy(func,obj) を使って書き直しました。
(2010-12-01修正) MSIE のクロスページ・リークがあったため、createList() で jQuery(this.list) に li 要素を追加してから、li 要素に a 要素を追加するように要素を DOM ツリーに追加する順番を変更しました。