コンテンツにスキップ

Top

地図(マップ)ライブラリ Leaflet 入門

HPに地図が乗っていたりしますが、多くはGoogle Mapでやっていると思います。
でもGoogle Mapって条件しだいでは有料になるんですよね。

無料でHPに地図を載せたい!という方には Javascript の 地図ライブラリのLeafletが役に立ちます。

これを使えばGoogleMapのようにHPに地図を表示したりできます。

ので、それの説明...と言いたいところですが、ここでは割愛します。

Leaflet はいわゆる「リアル」な地図にいろいろできるだけではなく、手書きの図形にマーカーやラインを割り当てたりもできるのです。

自作の手書きマップにマーカーをつけたりしてちょっとしたお宝地図をつくったりなんかもできちゃいます。

そういった、画像にマーカーやラインをつけたりする機能について説明します。

公式HP

公式HPは https://leafletjs.com/ になります。

このライブラリの製作者がウクライナ人なので、今、ページに遷移するとまずウクライナのおかれている現状の説明や寄付を募ったりしているので、一瞬、変なサイトに飛んだかな?と思ってしまうかもしれませんがそんなことはないので寄付したい人はしましょう。

とはいっても普通の使い方

とは言ってもなんかとりあえず世界地図を画面にだして、Leafletってこういうことできるんだよ、というのを見てもらうと、

sample01.html

<!DOCTYPE html>
<html>
    <head>
        <title>Leaflet Sample 01</title>

        <link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css">

        <script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>

        <style>
            html, body, #map {
                height: 100%;
            }
        </style>
    </head>

    <body>
        <div id="map"></div>
    </body>

    <script>
        var map = L.map('map').setView([0, 0], 1);

        L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
            attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
        }).addTo(map);
    </script>
</html>

というhtmlファイルを作成してダブルクリックしてブラウザで表示すると、

タイトル

という感じの地図が表示されます。

たったこれだけのソースでこうなっちゃうってすごいライブラリですよね。

余談ですが、

L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
の "{z}/{x}/{y}" という書き方、昔からあるそうで、zがズーム、xが経度、yが緯度の値で、それをいれたらサーバーがそれにあった座標の画像データを返却してくれる、というルールで実装してくれています。
たいていの地図系のサーバはこの書き方を踏襲してくれているので、openstreetmap以外でも同じような指定の仕方をする場合があります。

用意した画像を表示

以下の宝の地図(treasure_map.png、幅:960px、高さ:768px)をLeafletライブラリ上で表示してみましょう。

タイトル

以下のようなHTMLファイルを用意します。画像ファイルはhtmlファイルと同じ場所においてください。

sample02.html

<!DOCTYPE html>
<html>
    <head>
        <meta http-equiv="content-language" content="ja">
        <title>Leaflet Sample 02</title>
        <link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css">
        <script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
        <style>
            /*
            html, body, #map {
                height: 100%;
            }
            */
            #map {
                height: 768px;
            }
        </style>
    </head>

    <body>
        <div id="map" />
    </body>

    <script>
        var map = L.map('map', {
            crs: L.CRS.Simple,
            minZoom: 0,
            maxZoom: 0
        });

        var bounds = [[0,0], [384, 480]]; // (y,xの順なので注意)
        var image = L.imageOverlay('./treasure_map.png', bounds).addTo(map);
        map.fitBounds(bounds); // これで表示される。これないと表示されない
    </script>
</html>
ブラウザで表示したら宝の地図がLeaflet上に表示されたと思います。

ではソースの中身を説明をします。

<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css">

<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>

は、 Leaflet の css と javascript です。これがないとどうしようもないです。
Leafletのダウンロード画面からダウンロードしたzip内にあるleaflet.cssとleaflet.jsをローカルにおいて直接アクセスしても当然問題ないです。
ここでは直接Web上のjsとcssにアクセスするようにしています。

<style>
    html, body, #map {
        height: 100%;
    }

ですが、このライブラリの特性上、 <div id=map の「高さ」を必ず指定してあげないと画像が表示されないのです。

ここでは % 指定したのでhtmlとbodyにもheightを指定しましたが、px指定であれば #map にのみ設定すればよいです。

ので、

<style>
    #map {
        height: 768px;
    }

と置き換えても問題ないです。(この場合はhtmlとbodyにheight指定は不要)

次に、地図ではなく任意の画像でマップオブジェクトを生成するために以下を記述します。

var map = L.map('map', {
    crs: L.CRS.Simple,
    maxZoom: 0,
    minZoom: 0
});

のように指定します。

ここで、csrに L.CRS.Simple を指定すると、本来なら 緯度(-90~90)、経度(-180~180)で指定していた値の範囲が「自分で指定した範囲」に制限されます。
表示する画像のピクセル値にならないので注意が必要です。
(とはいっても、画像のピクセル値をそのまま範囲に指定すれば結果的にピクセル値になるので、ふつうはピクセル値なのですが、例えば画像サイズ関係なく、縦横0~256の範囲に指定とかもできる、というだけです)

maxZoom:0 や minZoom:0 はズームをできなくしています。
これはここで必要なパラメータでは全くないのですが、指定しないと、以下のboundsの説明の時にズームが自動で動いて訳が分からなくなるため、挙動をわかりやすくするために無効にしているだけです。

var bounds = [[0,0], [384, 480]]; // (y,xの順なので注意)
これが任意で決める範囲です。この範囲で座標などが制限されます。
今回用意している画像は 幅960px、高さ768pxの画像なのに、あえて半分の480px,384pxで指定しています。
これはちょっと変というか、本来は画像のpxそのままがわかりよいのでよいと思います。
ただ、それだと範囲指定がどういう機能かわかりにくいのであえて半分にしています。

次にマップオブジェクト上に画像(treasure_map.png)をのっけます。

var image = L.imageOverlay('./treasure_map.png', bounds).addTo(map);

でもこれだけでは表示されません。setViewかfitBoundsしないといけません。ここではfitBoundsを使ってみます。

map.fitBounds(bounds);
これで画像がboundsで指定した範囲で表示されます。
以下のような画像が出ていると思います。

タイトル

ここで先ほど無効にしたZoomを有効にしてみましょう。

var map = L.map('map', {
    crs: L.CRS.Simple,
    maxZoom: 5,
    minZoom:-5 
});

すると、ぴったりフィットするように勝手にズームしました。

タイトル

もし最初からこうしていたら、なんか範囲指定したのに毎回同じ画面いっぱいに表示されてわけわからん、となったかと思います。

ので、いったんZoomを無効にしてみたわけです。

ここまでで、画像をマップオブジェクトにして表示するのは終了です。

クリックした場所の座標を表示する

マップオブジェクト上の座標がどうなっているのか見てみましょう。
以下のhtmlを作成してください。クリックすると座標が表示されます。

sample03.html

<!DOCTYPE html>
<html>
    <head>
        <meta http-equiv="content-language" content="ja">
        <title>Leaflet Sample 02</title>
        <link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css">
        <script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
        <style>
            #map {
                height: 768px;
                width : 960px;
            }
        </style>
    </head>

    <body>
        <div id="map" />
    </body>

    <script>
        var map = L.map('map', {
            crs: L.CRS.Simple,
            maxZoom: 3,
            minZoom: -3
        });

        var bounds = [[0,0], [384, 480]];

        var image = L.imageOverlay('./treasure_map.png', bounds).addTo(map);

        map.setView([384 / 2, 480 / 2], 0); // 表示される中心の座標

        map.on('click', function(e) {
            L.popup().setLatLng(e.latlng)
            .setContent( '<p>(' + e.latlng.lng + ',' + e.latlng.lat + ')</p>')
            .openOn(map);
        });
    </script>
</html>

任意の場所をクリックすると座標が表示されます。

タイトル

これはZoom In、Zoom Outしても関係ないです。
タイトル

このように、拡大していても表示される座標は変わりません。
Zoomするたびに座標が変わっていたらいろいろ面倒ですので変わらないのは助かりますね。

そしてこの座標は繰り返しになりますが、ピクセル位置ではありません。

var bounds = [[0,0], [384, 480]];

でx,y座標の範囲を(0, 0)~(480, 384)の範囲にしたので、その範囲になります。
(わかりにくいですが、boundsでは高さ、幅の順で範囲を指定するのでxとyが逆になります)

例えば、boundsを画像と同じサイズにしてみましょう。

var bounds = [[0,0], [768, 960]];

setViewする座標も変えないと表示位置がずれるので合わせて直します。

map.setView([768 / 2, 960 / 2], 0); // 表示される中心の座標

x,y座標の範囲を(0, 0)~(960, 768)の範囲になったので、クリックしたときの座標とピクセル位置が一致します。
タイトル

正直このほうがわかりよいと思うので、特に問題がなければboundsで指定する範囲は画像の高さ幅にしたほうがよいと思います。

そのうえで、指定の範囲内に描画されるかどうか、ですが、Zoomを用いれば範囲内に収めることが可能となります。

では次に、マーカーを設置してみましょう。

マーカーを表示

デフォルトのマーカーを表示してみましょう。
以下のhtmlファイルを作成して表示してみましょう。

sample04.html


タイトル

簡単ですね。指定した座標にマーカーが表示されています。
このマーカーへのオプションはいろいろあります。
色を変えたり大きさを変えたりは基本的にはcssで行います。

オリジナルの画像マーカーを表示

マーカーをドラッグして移動する

ドラッグの終了を検知する

ドラッグの終わりを知りたい、というのがあると思います。
その場合はdragendというイベントがあるのでこれを設定すればドラッグ終了時にコールバックされます。
ドラッグの終了とは?と思うかもしれませんが、単純に左クリックを離したタイミングです。
終了ではなくドラッグの開始を知りたければdragstartというイベントもありますがここでは扱いません。

よくわからないのが、まったく同じ挙動をする moveend というイベントもあるのですが、差がわかりません...

ではサンプルを見てみましょう。 dragend のタイミングでポップアップが出ます。


気を付けてほしいのは、dragの時とdragendの時とで、イベントに含まれる値が異なることです。
dragにはあった、 e.latlng はdragendイベント時に来る e には含まれません。
e.target.getLatLng() で latlang を取得しないといけないのです。
なぜかはわかりませんし面倒ですがしようがないです。


これでドラッグ終わりに何かする、といった処理が可能となります。

マーカーをカスタマイズする

マーカーを任意の画像に変更できます。

公式ページのサンプル から 画像を取得します。

マーカーの画像を変えるには、以下のようにすればよいだけです。マーカー画像はhtmlと同じディレクトリにあるものとします。


全体のhtmlは以下です。


では表示してみましょう。見事に画像が変わりましたね!

マーカーをカスタマイズする2

マーカーを画像ではなく、円だとか図形に置き換えたい場合はdivを使えばできます。
以下のようにclassにcssを設定すればその通りの画像となります。


全体のhtmlは以下です。


簡単に変わりましたね!

クリックしたマーカーの座標を取得する

内部的に特定のマーカーの座標を取得したい場合があると思います。
clickというイベントを使うと容易に座標を取得できます。

クリックされたのがどのマーカーなのか特定する

座標が固定で分かっていれば、クリックされたのがどのマーカーなのか座標から逆残すればよい。

それに対し、任意にドラッグなどして座標が変わってしまったマーカーがクリックされた場合、どのマーカーがクリックされたのかわからない。

ので、配置前に事前に内部的なIDをマーカーに振っておく方法について述べる。

といっても、マーカーに任意の変数(ここではid)を与えて番号を振るだけ。

クリックされた時のコールバック関数内で自身のidの値をみればどのマーカーがクリックされたのかがわかる。

線を引く

線を引いてみましょう。2点の座標を指定すれば線が引けます。
以下のように色や太さなども変えれます。


全体のhtmlは以下です。

簡単ですね!