leafletはじめました

当エントリは、 FOSS4G Advent Calendar 2011 の12/19分として投稿したものです。ぜひ他の方のエントリも御覧下さい。
(錚々たる面々の中に割り込んでしまってビクビクしておりますが、くじけず最後まで頑張ります!)

こんにちは。monomotiです。「ちずぶらり」という古地図/絵地図アプリのデータのオーサリングやサーバ・web周りの開発をやっています。
その中でしばらくleafletを触る機会がありましたので、いろいろレポートしたいと思います。

leafletとは

釈迦に説法とは思いますが、一応このleafletとは何か説明しておきますと、JavaScriptで書かれたタイルベースの地図クライアント・ライブラリです。
その特徴は、

軽量(フル装備で69KB)!
動作がヌルヌルしていてお洒落!

というものです。ヌルヌル感の例をあげると、ズームは離散ズームなのですが、ズームチェンジ時にちょっとしたアニメーションが入っていてガタ付きを感じさせないようになっています。また、ピンチイン等でズームレベル0から1に向かってズームインするとき、途中で手を止めてもズームレベル1にヌルッとズームインしてくれます。こういう動作はユーザには結構大事みたいで、同じ機能の物を作って見せてもleafletとそれ以外では反応がかなり違います。これだけでも、僕にとってはlealfletを使いたくなる十分な理由になります。

はじめの一歩

では早速、使い方を。まずはファイルの設置。
http://leaflet.cloudmade.com/download.htmlからパッケージをDLして解凍します。解凍されたフォルダの中にdistというフォルダがあるので、この中身を任意のフォルダにコピーします。

あとは、HTMLのhead要素に次のようにリンクを入れれば準備完了です。

<head>
    <meta http-equiv="content-type" content="text/html; charset=UTF-8"/>         
    <title>Leaflet Cho Kantan</title>
    <link rel="stylesheet" href="leaflet/leaflet.css" />
    <script src="leaflet/leaflet.js"></script>
</head>

なお、jsファイルはbuild/build.html で必要なコンポーネントだけを選択してビルドすることで、より小さなサイズにすることができます。

では、地図を表示させてみましょう。HTMLに次のdiv要素があるとして、

<body>
    <div id="map"></div> <!-- これを地図のコンテナ要素にする -->
</body>

下のようなJavaScriptを書きます。

// ベースタイルのレイヤを定義
// {z}、{x}、{y}はそれぞれズームレベル、タイルの水平方向インデックス、タイルの垂直方向インデックスのプレースホルダ。
var tileUrl = 'タイルのURL{z}_{x}_{y}.jpg', 
    tileAttribution = 'タイルの権利帰属等',
    baseLayer = new L.TileLayer(tileUrl, {maxZoom: 18, attribution: tileAttribution });

// マップオプジェクト。引数はコンテナ要素のIDまたはコンテナ要素
var map = new L.Map('map'); 

// マップにベースタイルを追加する
map.setView(new L.LatLng(34.637728,135.40889713).addLayer(baseLayer);

これで地図が表示されます。

いろいろ弄れる

例えば投影はEPSG:3857、4326、3395が予め定義されていますが、独自の投影を定義する事も出来ます。

// 投影座標系を定義
var  myProj0001 = L.Util.extend({}, L.CRS, {
     code: 'MYPROJ:0001',

     projection: {
          project: function(latlng) {
               //経緯度latlngを平面座標に変換するコードをここに書く
               
               return new L.Point(平面座標X, 平面座標Y);
          },
          unproject: function(point, unbounded) {
               // 平面座標pointを経緯度に変換するコードをここに書く

               return new L.LatLng(緯度, 経度);
          }
     },
     transformation: new L.Transformation(1,0,1,0)    
});

// 投影座標系を指定してマップオブジェクトを生成。     
var map = new L.Map('map',{crs:myProj0001});

これで手書きの馬鹿地図なんかも表示出来るようになります。オープンソースって素敵ですね。

面倒くさがりな貴方に(いや僕に)うれしいAPI

APIGoogle Map APIにかなり似ていて、Google Mapsを触った事がある人なら特にドキュメントを調べる必要もなくコーディング出来ます。さらに、leafletにはlatLngToLayerPointやcontainerPointToLayerPointといった気の利いた座標変換メソッドがあって、これが結構便利です。
例えば僕は、センターマーカの表示なんかに使ったりしています。

var cmk;
function setCenterMaker(){
// (jQuery使ってます)
    if (!cmk){
        cmk = $(document.createElement("img"));
        cmk.attr("src", "./images/cmk.gif").css({position:"absolute",zIndex:999});
        $("#map").append(cmk);
    }
    var cll = lLMap.getCenter();
    var cxy = lLMap.layerPointToContainerPoint(map.latLngToLayerPoint(cll));
    cmk.css({
        top: cxy.y - 20 + "px",
        left: cxy.x - 20 + "px"
    });
}
$(window).resize(setCenterMaker);


「コンテナの幅と高さを取って真ん中に置けばいいんじゃないの?」と言われそうですが、自分でサイズを取得するとよく間違えるので、僕はコンテナ要素を極力気にしたくないんです...。上記のコードだと、コンテナを意識するのはセンターマーカを生成する時だけになるので、この方が楽な気がします(ちょっと遅くなっていると思いますが)。

気をつけるべき点

最後に、留意すべき点を1つあげておきます。それは、他のHTML要素に対する地図コンポーネントの振る舞いが、ブラウザによって異なるという事です。
例えば、マーカをクリックしてマーカの属性をポップアップで表示する、というのはよくやる事だと思います。leafletにはGoogle Maps APIのinfowindowと同じようなpopUpというクラスがあり非常に使いやすいのですが、

実際の業務では自前のHTML要素をオーバーレイさせる事の方が多いと思います。

僕も下のような感じでやってみました。

<div id="map" style="width:100%; height:100%"></div>
<div id="popUp" >
	<p id="popUpContent" ></p>
	<div class="closeButton">&nbsp;x&nbsp;</div>
</div>
var mk1 = new L.Marker(new L.LatLng(34.637728,135.40889713),{message:"Hello!"});
mk1.on("click",function(e){
	showPopUp(e.target.options.message);
});
map.addLayer(mk1);

$(".closeButton").click(function(){
	$(this).parent().hide();
});

function showPopUp(content){
	$("#popUpContent").html(content);
	var dWidth = $(window).width() / 2,
	dHeight = $(window).height() /2,
	pH = dHeight / 2,
	pW = dWidth / 2;
	$("#popUp").show().css("top",pH + "px").css("left",pW + "px").height(dHeight).width(dWidth);
}

が、FireFoxだと何も表示されず、悩むこと十数分(悩み過ぎ)。エラーログには何もないし、何でだろう?と思いながらベースレイヤを切り替えてみたら...

そこに居たのか...。どうやら、FireFoxではレイヤオブジェクトは後に出現するHTML要素よりも前に出てきてしまうようです。こういう事が他にもあるのかないのか。
あと、asusタブレットではタイルの配置がおかしくなるという不具合があります(Eee Pad Transformerで確認)。

まとめ

leafletは、ルック&フィールやAPIが非常に洗練されていてとても使いやすいです。お客さんにも受けが良いです。しかし、クロスブラウザ環境での動作についてはまだまだ問題がありそうです。ひととおり挙動が分かってしまえば大した事はないですが、癖が分かっていない今の状況では、ちょっと怖くて僕はまだ実戦(製品)で使えていません。しかしできるだけ早く実戦で使ってみたいので、これからどんどん試作をして検証していきます。また、時間があればプロジェクトにも貢献したいなと思います。

レポートは以上です! 次はnissyyuさんです!

(こんなエントリで良かったんでしょうか。FOSS4Gソングの作り方の方がよかったかな...)