BODIK APIの使い方(Javascript編)機能追加1

BODIK APIの使い方(Javascript編)」で、Javascriptを使ったWebアプリケーションで、BODIK APIをどのように使うのかをご説明しています。課題2では、BODIK APIを呼び出した結果を地図に表示するプログラムを記述しました。

課題2のプログラムに機能を追加することを考えてみましょう。

地図をクリックしたとき、古いマーカーを削除する

課題2のプログラムでは、地図をクリックすると、そのクリックされた地点を中心に一定の半径内にある「AED」をBODIK APIで検索し、その結果を地図上にマーカーとして表示しました。クリックするたびに、マーカーが追加されてしまいます。
これはかっこ悪いので、新しくクリックしたときは、古いマーカーを消すようにしてみましょう。

対策1

基本的な考え方として、「マーカーを覚えておいて、1つずつ削除する」でやってみましょう。

「マーカーを覚えておく」は、配列に入れておけばいいですね。

marker_list = [];
marker_list.push(marker);

「マーカーを削除する」は、Leafletの「removeFrom」メソッドを使えます。

marker.removeFrom(map);

プログラムは次のようになります。

配列「marker_list」を関数(initやshowMarker)の外側(グローバル)で定義していることが重要です。

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8" />
    <title>BODIK API</title>
    <link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"
        integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY="
        crossorigin="" />
    <script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"
        integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo="
        crossorigin=""></script>

    <script type="text/javascript">
        let map = null;
        let marker_list = null;    // マーカーを覚えておく配列
        function init() {
            map = L.map('map', { zoomControl: false });
            //  指定された緯度経度を中心とした地図
            let lat = 33.59334325082392;
            let lon = 130.35598920962553;
            let center = [lat, lon];
            map.setView(center, 14);

            //地理院地図の標準地図タイル
            let gsi = L.tileLayer('https://cyberjapandata.gsi.go.jp/xyz/std/{z}/{x}/{y}.png',
                { attribution: "<a href='https://maps.gsi.go.jp/development/ichiran.html' target='_blank'>地理院タイル</a>" });
            gsi.addTo(map);

            //showMarker(lat, lon);
            map.on('click', onMapClick);
        }

        function onMapClick(e) {
            let latlng = e.latlng;
            let lat = latlng.lat;
            let lon = latlng.lng;
            showMarker(lat, lon);
        }

        function showMarker(lat, lon) {
            let api_server = 'https://wapi.bodik.jp';
            let api = 'aed';
            let distance = 20000;
            let maxResults = 100;
            let api_url = `${api_server}/${api}?select_type=geometry&lat=${lat}&lon=${lon}&distance=${distance}&maxResults=${maxResults}`;
            fetch(api_url)
            .then(response => response.json())
            .then(data => {
                if (marker_list) {                    // 古いマーカーがあったら
                    for (let marker of marker_list) { // 配列から順次取り出し
                        marker.removeFrom(map);       // マップから削除する
                    }
                    marker_list = null;
                }
                let features = data['resultsets']['features'];
                marker_list = [];
                for (let feature of features) {
                    let properties = feature['properties'];
                    let popup = L.popup().setContent(properties['name']);

                    let geometry = feature['geometry'];
                    let location = geometry['coordinates'];
                    let marker = L.marker([location[1], location[0]]).addTo(map);
                    marker.bindPopup(popup);

                    marker_list.push(marker);         // 配列に記録する
                }

            });
        } 
    </script>

</head>

<body onload="init()">
    <h2>BODIK API program #2</h2>
    <div class="map" id="map" style="width:800px;height:600px"></div>
</body>

</html>

これは、わかりやすいのですが、、、削除するときに、「for」で回すところをもうすこしスマートにしたいですね。

対策2

Leafletに「オブジェクトをまとめて扱う」機能はないのかな?と探していたら、「layerGroup」というのを見つけました。

https://leafletjs.com/reference.html#layergroup

説明にも、「まとめて地図に表示したり、削除したりできる」とあるので、これを使ってみましょう。

今回の場合、layerGroupを作るときに、マーカーの配列をパラメータに与えればいいみたいです。
また、マーカーを作成した時点では地図に表示せず、mapの「addLayer」メソッドを使って、作成したLayerを地図に表示するようです。

marker_list = [];
let marker = L.marker([location[1], location[0]]);   // マーカーを作成するだけ。この時点で地図に表示しない。
marker_list.push(marker);
   ・・・・
marker_layer = L.layerGroup(marker_list);
map.addLayer(marker_layer);                          // 地図に表示する。

プログラムにすると、次のようになります。

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8" />
    <title>BODIK API</title>
    <link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"
        integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY="
        crossorigin="" />
    <script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"
        integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo="
        crossorigin=""></script>

    <script type="text/javascript">
        let map = null;
        let marker_list = null;    // マーカーの配列
        let marker_layer = null;   // マーカーのレイヤー
        function init() {
            map = L.map('map', { zoomControl: false });
            //  指定された緯度経度を中心とした地図
            let lat = 33.59334325082392;
            let lon = 130.35598920962553;
            let center = [lat, lon];
            map.setView(center, 14);

            //地理院地図の標準地図タイル
            let gsi = L.tileLayer('https://cyberjapandata.gsi.go.jp/xyz/std/{z}/{x}/{y}.png',
                { attribution: "<a href='https://maps.gsi.go.jp/development/ichiran.html' target='_blank'>地理院タイル</a>" });
            gsi.addTo(map);

            //showMarker(lat, lon);
            map.on('click', onMapClick);
        }

        function onMapClick(e) {
            let latlng = e.latlng;
            let lat = latlng.lat;
            let lon = latlng.lng;
            showMarker(lat, lon);
        }

        function showMarker(lat, lon) {
            let api_server = 'https://wapi.bodik.jp';
            let api = 'aed';
            let distance = 20000;
            let maxResults = 100;
            let api_url = `${api_server}/${api}?select_type=geometry&lat=${lat}&lon=${lon}&distance=${distance}&maxResults=${maxResults}`;
            fetch(api_url)
            .then(response => response.json())
            .then(data => {
                if (marker_layer) {                    // 古いレイヤがあったら
                    map.removeLayer(marker_layer);     // レイヤーを削除する 
                    marker_layer = null;
                }
                let features = data['resultsets']['features'];
                marker_list = [];
                for (let feature of features) {
                    let properties = feature['properties'];
                    let popup = L.popup().setContent(properties['name']);

                    let geometry = feature['geometry'];
                    let location = geometry['coordinates'];
                    let marker = L.marker([location[1], location[0]]);    // 地図に表示しない
                    marker.bindPopup(popup);
                    marker_list.push(marker);         // 配列に記録する
                }

                if (marker_list.length > 0) {
                    marker_layer = L.layerGroup(marker_list);   // レイヤーを作成
                    map.addLayer(marker_layer);                 // 地図に表示する
                }
            });
        } 
    </script>

</head>

<body onload="init()">
    <h2>BODIK API program #2</h2>
    <div class="map" id="map" style="width:800px;height:600px"></div>
</body>

</html>

こんなやり方もありますね。

ひらの