■Ajax : Google Mapsを表示する

 Ajax (Asynchronous JavaScript+XML)を使った有名なものとしてGoogle Mapsがあります。ここではGoogle Mapsで使われている技術の一部をサンプルを作成して実際に動かしてみます。

注意:現在はサーバー名やデータ方式が初期のものと異なるため動作しないサンプルがあります。説明も古いものになっています。参考までにということでお願いします。現在Google Mapsは登録すればAPIを利用して表示させたり、いろいろなことができるようになっています。(2005年11月17日午後4時11分改訂)


 まず、最初にGoogle Mapsを表示してみましょう。実際のスクリプトは以下のようになります。

サンプルを実行する
<html>
<head>
<title>Google Mapsのデータを表示する</title>
</head>
<body>
<script type="text/javascript"><!--
for (y=1600; y<1603; y++)
{
for (x=3616; x<3620; x++)
{
document.write('<img src="http://mt2.google.com/mt?v=w2.6&x='+x+'&y='+y+'&zoom=5">');
}
document.write("<br>");
}
// --></script>
</body>
</html>

 Google Mapsが表示している画像のURLはhttp://mt2.google.com/mt?v=w2.6&x=3620&y=1600&zoom=5のようになります。以下のURLを直接ブラウザに入力してみてください。

http://mt2.google.com/mt?v=w2.6&x=3620&y=1600&zoom=5

 道路がある画像が表示されたはずです。ここでx=3620をx=3621にして画像を表示してみてください。さきほどの画像の右側の地点の画像が表示されたはずです。同様にy=1601とすると下の画像が表示されます。zoom=2とすると、より詳細な地図になります(サンプルを実行:拡大表示される。0〜5までの値を指定できる)。Google Mapsの画像ブロックは128ピクセル×128ピクセルのサイズになっています。
 このサンプルでは一度表示した地図を変更できるようになっていません。Google Mapsでは地図を移動させたりすることができます。そこで、今度は画像を入れ替えて移動しているように見せるようにします。これが以下のサンプルになります。

サンプルを実行
<html>
<head>
<title>Google Mapsのデータを表示する</title>
<script type="text/javascript"><!--
function viewMap(fObj)
{
sx = parseInt(fObj.gx.value);
sy = parseInt(fObj.gy.value);
for (y=0; y<3; y++)
{
for (x=0; x<3; x++)
{
imgURL = "http://mt2.google.com/mt?v=w2.6&x="+(sx+x)+"&y="+(sy+y)+"&zoom=5";
document.images["map"+x+y].src = imgURL;
}
}
}
// --></script>
</head>
<body>
<script type="text/javascript"><!--
serial = 0;
for (y=0; y<3; y++)
{
for (x=0; x<3; x++)
{
document.write('<img src="trans.gif" width="256" height="256" name="map'+x+y+'">');
}
document.write("<br>");
}
// --></script>
<form>
X:<input type="text" name="gx" value="3620"><br>
Y:<input type="text" name="gy" value="1600"><br>
<input type="button" value="地図表示" onClick="viewMap(this.form)">
</form>
</body>
</html>

 まず、透明なGIF画像を9枚の画像に設定します。9というのは横3枚、縦3枚にタイル状に画像が配置されているので、この数になります。フォームのテキストフィールドに入力された座標値から表示する画像のURLを求めます。このURLを画像のsrcプロパティに入れます。これを4枚分繰り返すことで地図を表示できます。この作業をより速く行えば地図をスクロール(移動)させたりすることができます。
 Google Mapsはボタンをクリックすることで地図を移動させることができます。かなりスムーズに地図がスクロールします。とりあえず、ボタンをクリックしたら地図をブロック単位で移動するようにします。これが以下のサンプルです。

サンプルを実行する
<html>
<head>
<title>Google Mapsのデータを表示する</title>
<script type="text/javascript"><!--
sx = 3620;
sy = 1600;
function viewMap()
{
for (y=0; y<3; y++)
{
for (x=0; x<3; x++)
{
imgURL = "http://mt2.google.com/mt?v=w2.6&x="+(sx+x)+"&y="+(sy+y)+"&zoom=5";
document.images["map"+x+y].src = imgURL;
}
}
}
// --></script>
</head>
<body>
<script type="text/javascript"><!--
for (y=0; y<3; y++)
{
for (x=0; x<3; x++)
{
document.write('<img src="trans.gif" width="256" height="256" name="map'+x+y+'">');
}
document.write("<br>");
}
// --></script>
<form>
 <input type="button" value="上" onClick="sy--;viewMap()"><br>
<input type="button" value="左" onClick="sx--;viewMap()">
<input type="button" value="右" onClick="sx++;viewMap()"><br>
 <input type="button" value="下" onClick="sy++;viewMap()">
</form>
</body>
</html>

 実際のGoogle Mapsではもう少しブロックを多くしています。これはスクロールした場合に余計なブロックが存在しないとマップが切れてしまうためです。
 次にマップ全体をスムーズにスクロールさせてみましょう。マップ全体をスクロールさせるために<div>タグで地図画像を囲み、<div>の表示座標をスタイルシートで指定します。ボタンがクリックされたらマップ全体を移動させます。過去にはwhile()を使ってスクロールさせる方法もありましたが、ここでは安全のためタイマー(setTimeout())を使って地図の表示座標をずらしていきます。指定された座標まで移動したらスクロール処理を中止します。

サンプルを実行する
<html>
<head>
<title>Google Mapsのデータを表示する</title>
<script type="text/javascript"><!--
sx = 3620;
sy = 1600;
mx = 0;
my = 0;
dx = 0;
dy = 0;
mapSize = 256;
oldX = oldY = newX = newY = 0;
function moveMap(dxx,dyy)
{
dx = dxx;
dy = dyy;
oldX = mx * mapSize;
oldY = my * mapSize;
mx = mx + dx;
my = my + dy;
newX = mx * mapSize;
newY = my * mapSize;
setTimeout("scrollMap()",10);
}
function scrollMap()
{
document.getElementById("mapData").style.left = oldX;
document.getElementById("mapData").style.top = oldY;
if ((oldX == newX) && (oldY == newY)) return;
oldX = oldX + dx*2;
oldY = oldY + dy*2;
setTimeout("scrollMap()",10);
}
// --></script>
</head>
<body>
<div id="mapData" style="position:absolute;top:0px;left:0px;">
<script type="text/javascript"><!--
for (y=0; y<3; y++)
{
for (x=0; x<3; x++)
{
document.write('<img src="http://mt2.google.com/mt?v=w2.6&x='+(sx+x)+'&y='+(sy+y)+'&zoom=5">');
}
document.write("<br>");
}
// --></script>
</div>
<form style="position:absolute;top:650px;left:10px">
?@<input type="button" value="上" onClick="moveMap(0,-1)"><br>
<input type="button" value="左" onClick="moveMap(-1,0)">
<input type="button" value="右" onClick="moveMap(1,0)"><br>
下<input type="button" value="?∫" onClick="moveMap(0,1)">
</form>
</body>
</html>

 次にスクロールした際に地図画像を読み込み延々と表示するようにしてみます。先ほどのプログラムで余計な部分を削除し単純に上から下にスクロールさせるだけのものにします。

サンプルを実行する
<html>
<head>
<title>Google Mapsのデータを表示する</title>
<script type="text/javascript"><!--
sx = 3620;
sy = 1600;
mapSize = 256;
dy = 1;
mapY = 0;
function viewMap()
{
for (y=0; y<3; y++)
{
for (x=0; x<3; x++)
{
imgURL = "http://mt2.google.com/mt?v=w2.6&x="+(sx+x)+"&y="+(sy+y)+"&zoom=5";
document.images["map"+x+y].src = imgURL;
}
}
}
// マップの横の画像を表示
function drawMapDataX()
{
for (x=0; x<3; x++)
{
imgURL = "http://mt2.google.com/mt?v=w2.6&x="+(sx+x)+"&y="+sy+"&zoom=5";
document.images["map"+x+"0"].src = imgURL;
}
}
function scrollMap()
{
mapY = mapY + dy;
if (mapY > mapSize)
{
mapY = 0;
sy = sy - 1;
viewMap();
}
document.getElementById("mapData").style.top = mapY;
setTimeout("scrollMap()",10);
}
// --></script>
</head>
<body onLoad="scrollMap()">
<div id="mapData" style="position:absolute;top:0px;left:0px;">
<script type="text/javascript"><!--
for (y=0; y<3; y++)
{
for (x=0; x<3; x++)
{
document.write('<img src="trans.gif" width="256" height="256" name="map'+x+y+'">');
}
document.write("<br>");
}
viewMap();
// --></script>
</div>
</body>
</html>

 延々とループ表示させる関数がscrollMap()です。マップサイズよりも<div>タグの座標値が大きくなったら0に戻します。これでマップ全体が一番上に移動します。この時にマップブロックの表示を1つずらしマップ全体を再描画します。回線速度の都合で読み込みが間に合わない場合があります。この場合はsetTimeout()の値を大きくしてください。1000とすると1秒ごとにスクロール処理が行われます。
 上記サンプルではマップ全体が上に戻ってしまうため見苦しい状態です。この処理を見せないようにするには、さらに<div>タグで囲み、この<div>タグにスタイルシートのclipを指定します。clipは指定した範囲のみ表示を行うものです。この値を調節してマップが上に戻ったのを見られないようにします。実際のサンプルは以下のものになります。

サンプルを実行する
<html>
<head>
<title>Google Mapsのデータを表示する</title>
<script type="text/javascript"><!--
sx = 3620;
sy = 1600;
mapSize = 256;
dy = 1;
mapY = 0;
function viewMap()
{
for (y=0; y<3; y++)
{
for (x=0; x<3; x++)
{
imgURL = "http://mt2.google.com/mt?v=w2.6&x="+(sx+x)+"&y="+(sy+y)+"&zoom=5";
document.images["map"+x+y].src = imgURL;
}
}
}
// マップの横の画像を表示
function drawMapDataX()
{
for (x=0; x<3; x++)
{
imgURL = "http://mt2.google.com/mt?v=w2.6&x="+(sx+x)+"&y="+sy+"&zoom=5";
document.images["map"+x+"0"].src = imgURL;
}
}
function scrollMap()
{
mapY = mapY + dy;
if (mapY > mapSize)
{
mapY = 0;
sy = sy - 1;
viewMap();
}
document.getElementById("mapData").style.top = mapY;
setTimeout("scrollMap()",10);
}
// --></script>
</head>
<body onLoad="scrollMap()">
<div style="position:absolute;top:0px;left:0px;width:640px;clip:rect(256px 1500px 768px 0px);">
<div id="mapData" style="position:absolute;top:0px;left:0px;width:1000px">
<script type="text/javascript"><!--
for (y=0; y<3; y++)
{
for (x=0; x<3; x++)
{
document.write('<img src="trans.gif" width="256" height="256" name="map'+x+y+'">');
}
document.write("<br>");
}
viewMap();
// --></script>
</div>
</div>
</body>
</html>

 実際には一方向ではなくいろいろな方向に移動するため処理は多少面倒になります。
 次にマップの上に表示されるふきだしやピンの処理を見てみます。ふきだしやピンをよく見ると影がついており、半透明でふちがぼやけています。これはαチャンネル付きPNG画像を利用しています。αチャンネルというのは透過情報のことで0〜255段階で不透明度を指定することができます。それでは実際にGoogle Mapsの上に半透明の影を持ったPNG画像を表示してみましょう。以下のサンプルはFirefox, Netscape 6以降, Safariで動作します。

サンプルを実行する
<html>
<head>
<title>Google Mapsのデータ+ふきだしを表示する</title>
</head>
<body>
<div id="mapData" style="position:absolute;top:0px;left:0px;z-Index:1;">
<script type="text/javascript"><!--
for (y=1600; y<1604; y++)
{
for (x=3616; x<3620; x++)
{
document.write('<img src="http://mt2.google.com/mt?v=w2.6&x='+x+'&y='+y+'&zoom=5">');
}
document.write("<br>");
}
// --></script>
</div>
<img src="pop.png" style="position:absolute;top:285px;left:210px;z-Index:10;">
</body>
</html>

 マップの上にふきだしが表示され半透明の影がGoogle Mapsの上に綺麗に表示されているはずです。(画像がいまいちとかふちが、ちょっと怪しいとかは無視してくださいf(^^;)
 しかし、Windows版のInternet Explorer (以下IE) ではαチャンネル付きのPNG画像を表示すると正しく反映されません。しかし、Google MapsではIEでも半透明の影を表示しています。これはフィルタ機能(Direct X)のAlphaIMageLoaderを使っています。これはIE5.5以上で利用することができます。このAlphaImageLoaderはフィルタ機能なのでスタイルシートで設定します。詳細はマイクロソフト社のページにありますので、そちらを参照してください(もしくは拙著改訂3版JavaScriptポケットリファレンス 425ページ)。実際のサンプルは以下のようになります。

サンプルを実行する
<html>
<head>
<title>Google Mapsのデータ+ふきだしを表示する</title>
</head>
<body>
<div id="mapData" style="position:absolute;top:0px;left:0px;z-Index:1;">
<script type="text/javascript"><!--
for (y=1600; y<1604; y++)
{
for (x=3616; x<3620; x++)
{
document.write('<img src="http://mt2.google.com/mt?v=w2.6&x='+x+'&y='+y+'&zoom=5">');
}
document.write("<br>");
}
// --></script>
</div>
<div style="position:absolute;top:285px;left:210px;z-Index:10;width:128px;height:128px;filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src='pop.png')"></div>
</body>
</html>

 これでIEでもαチャンネル付きのPNG画像が正しく表示されます。Mac版のIEはGoogle Mapsの対象外ですしαチャンネル付きのPNGも扱えないので除外します。
 ふきだしの部分はIEとそれ以外で表示するタグを変更しなければなりません。IEとそれ以外を分岐すれば良いのでdocument.allオブジェクト(コレクション:配列)が存在するかどうかで判別します。実際のサンプルは以下のようになります。

サンプルを実行する
<html>
<head>
<title>Google Mapsのデータ+ふきだしを表示する</title>
</head>
<body>
<div id="mapData" style="position:absolute;top:0px;left:0px;z-Index:1;">
<script type="text/javascript"><!--
for (y=1600; y<1604; y++)
{
for (x=3616; x<3620; x++)
{
document.write('<img src="http://mt2.google.com/mt?v=w2.6&x='+x+'&y='+y+'&zoom=5">');
}
document.write("<br>");
}
// --></script>
</div>
<script type="text/javascript"><!--
if (document.all)
{
document.write('<div style="position:absolute;top:285px;left:210px;z-Index:10;width:128px;height:128px;filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src=\'pop.png\')"></div>');
}else{
document.write('<img src="pop.png" style="position:absolute;top:285px;left:210px;z-Index:10;">');
}
// --></script>
</body>
</html>

 Google Mapsは地図をドラッグして移動させることができます。そこで、まずさきほどのふきだしをGoogle Maps上でドラッグできるようにします。ドラッグするにはドラッグされているかどうかなどのイベントを調べます。と書きたいところですがIEには、そのようなイベントが用意されていますが、他のブラウザでは用意されていなかったり微妙に動作が異なっています。そこで、ふきだし上でマウスボタンが押されたかを調べ押されている場合にドラッグ、ボタンが離されたらドラッグ終了という処理を組み込みます。以下のサンプルはFirefox, Netscape 6以上, Safariで動作するものです。プログラムを短くするためPNG画像の代わりにGIF画像を使っています。

サンプルを実行する
<html>
<head>
<title>Google Mapsのデータ+ふきだしを表示する</title>
<script type="text/javascript"><!--
flag = false;
mx = my = 0;
window.document.onmousemove = dragImg;
window.document.onmouseup = dragOff;
function dragOn(){
flag = true;
offx = mx - parseInt(document.images["aPop"].style.left);
offy = my - parseInt(document.images["aPop"].style.top);
}
function dragOff(){ flag = false; }
function dragImg(e)
{
mx = e.pageX;
my = e.pageY;
if (!flag) return;
document.images["aPop"].style.top = my - offy;
document.images["aPop"].style.left = mx - offx;
return false;
}
// --></script>
</head>
<body>
<div id="mapData" style="position:absolute;top:0px;left:0px;z-Index:1;">
<script type="text/javascript"><!--
for (y=1600; y<1604; y++)
{
for (x=3616; x<3620; x++)
{
document.write('<img src="http://mt2.google.com/mt?v=w2.6&x='+x+'&y='+y+'&zoom=5">');
}
document.write("<br>");
}
// --></script>
</div>
<img src="pop.gif" id="aPop" style="position:absolute;top:285px;left:210px;z-Index:10;" onMousedown="dragOn();return false">
</body>
</html>

 このままではIEでは動作しません。これはイベントモデルが少し異なっているためです。IEではイベント情報はeventオブジェクト内に入っているため、このオブジェクトを参照するように変更します。実際のサンプルは以下のようになります。

サンプルを実行する
<html>
<head>
<title>Google Mapsのデータ+ふきだしを表示する</title>
<script type="text/javascript"><!--
flag = false;
mx = my = 0;
window.document.onmousemove = dragImg;
window.document.onmouseup = dragOff;
function dragOn(){
flag = true;
offx = mx - parseInt(document.images["aPop"].style.left);
offy = my - parseInt(document.images["aPop"].style.top);
}
function dragOff(){ flag = false; }
function dragImg(e)
{
mx = event.x;
my = event.y;

if (!flag) return;
document.images["aPop"].style.top = my - offy;
document.images["aPop"].style.left = mx - offx;
return false;
}
// --></script>
</head>
<body>
<div id="mapData" style="position:absolute;top:0px;left:0px;z-Index:1;">
<script type="text/javascript"><!--
for (y=1600; y<1604; y++)
{
for (x=3616; x<3620; x++)
{
document.write('<img src="http://mt2.google.com/mt?v=w2.6&x='+x+'&y='+y+'&zoom=5">');
}
document.write("<br>");
}
// --></script>
</div>
<img src="pop.gif" id="aPop" style="position:absolute;top:285px;left:210px;z-Index:10;" onMousedown="dragOn();return false">
</body>
</html>

 Safari, Firefox, Nestcapeとの違いは赤字で示した2行だけです。IEとそれ以外を判別するには前述したdocument.allを使って調べます。Safari, Firefox, Netscape, IEで動作するサンプルは以下のようになります。

サンプルを実行する
<html>
<head>
<title>Google Mapsのデータ+ふきだしを表示する</title>
<script type="text/javascript"><!--
flag = false;
mx = my = 0;
window.document.onmousemove = dragImg;
window.document.onmouseup = dragOff;
function dragOn(){
flag = true;
offx = mx - parseInt(document.images["aPop"].style.left);
offy = my - parseInt(document.images["aPop"].style.top);
}
function dragOff(){ flag = false; }
function dragImg(e)
{
if (document.all)
{
mx = event.x;
my = event.y;
}else{
mx = e.pageX;
my = e.pageY;
}
if (!flag) return;
document.images["aPop"].style.top = my - offy;
document.images["aPop"].style.left = mx - offx;
return false;
}
// --></script>
</head>
<body>
<div id="mapData" style="position:absolute;top:0px;left:0px;z-Index:1;">
<script type="text/javascript"><!--
for (y=1600; y<1604; y++)
{
for (x=3616; x<3620; x++)
{
document.write('<img src="http://mt2.google.com/mt?v=w2.6&x='+x+'&y='+y+'&zoom=5">');
}
document.write("<br>");
}
// --></script>
</div>
<img src="pop.gif" id="aPop" style="position:absolute;top:285px;left:210px;z-Index:10;" onMousedown="dragOn();return false">
</body>
</html>

 実際のGoogle Mapsでは地図全体をドラッグしながら地図データを読み込みリアルタイムに表示します。今までの方法を組み合わせると、このような処理を実現することができます。以下のサンプルはクリッピングしていないものです。(クリック位置を保存していないので、ちょっと変なところがあります。その他、妙な部分がありますが、ご容赦ください)

サンプルを実行する
<html>
<head>
<title>Google Mapsのデータ+ふきだしを表示する</title>
<script type="text/javascript"><!--
mapBaseSx = sx = 3610;
mapBaseSy = sy = 1600;
dx = 1;
dy = 1;
mapX = 0;
mapY = 0;
blockSize = 256; // ブロックサイズ
flag = false;
mx = my = 0;
baseX = baseY = 0;
dName = "dragImage";
window.document.onmousemove = dragImg;
window.document.onmouseup = dragOff;
function dragOn(){
flag = true;
baseX = mx;
baseY = my;
}
function dragOff()
{
flag = false;
mapBaseSx = sx;
mapBaseSy = sy;
}
function dragImg(e)
{
if (document.all)
{
mx = event.x;
my = event.y;
}else{
mx = e.pageX;
my = e.pageY;
}
if (!flag) return;
mapX = baseX - mx;
mapY = baseY - my;
sx = mapBaseSx + mapX / blockSize;
sy = mapBaseSy + mapY / blockSize;
viewMap();
document.getElementById("mapData").style.left = -mapX % blockSize;
document.getElementById("mapData").style.top = -mapY % blockSize;
return false;
}
function viewMap()
{
var x,y,imgURL;
for (y=0; y<3; y++)
{
for (x=0; x<3; x++)
{
imgURL = "http://mt2.google.com/mt?v=w2.6&x="+Math.floor(sx+x)+"&y="+Math.floor(sy+y)+"&zoom=5";
document.images["map"+x+y].src = imgURL;
}
}
}
// --></script>
</head>
<body>
<div id="mapData" style="position:absolute;top:0px;left:0px;">
<script type="text/javascript"><!--
for (y=0; y<3; y++)
{
for (x=0; x<3; x++)
{
document.write('<img src="trans.gif" width="256" height="256" name="map'+x+y+'">');
}
document.write("<br>");
}
viewMap();
// --></script>
</div>
<img src="trans.gif" id="dragImage" style="position:absolute;top:0px;left:0px;width:768px;height:768px;z-Index:10;" onMousedown="dragOn();return false">
</body>
</html>

 最後にクリッピング領域を設定すればできあがりです。