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

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

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

検索結果全体を地図に見えるようにする

課題2のプログラムでは、地図をクリックすると、そのクリックされた地点を中心に一定の半径内にある「AED」をBODIK APIで検索し、その結果を地図上にマーカーとして表示しました。

検索半径が大きい場合や、地図の端っこでクリックした場合など、表示されている地図の外側にもマーカーが立つケースがあります。それらを確認するためには利用者が地図の表示位置や縮尺を変更しなければいけません。

地図の縮尺や表示位置を自動的に調整して、全体が見えるようにしたい。

対策

なにかいい方法がないかな?とLeafletのマニュアルを見ていたら、「Bounds(境界)」というものがありました。
Boundsの使い方 https://leafletjs.com/reference.html#bounds

  1. Boundsに点を追加していくと、外接矩形を得られる。
  2. 地図表示をBoundsに合わせることができる。(map.fitBoundsメソッド)

これを使えばなんとかなりそう。

まず、Leafletの「latLngBounds」メソッドを使って、検索の中心点で境界を初期化しよう。

let center = [ lat, lon ];
let bounds = L.latLngBounds(center, center);

マーカーを作成したとき、境界の「extend」メソッドを使って、マーカーの位置を境界に追加する。

let pos = [ marker_lat, marker_lon ];
bounds.extend(pos);

「地図表示を境界に合わせる」は、Leafletの「fitBounds」メソッドを使います。

map.fitBounds(bounds);

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

<!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;
        let areaCircle = 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 = 2000;
            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;
                }
                if (areaCircle) {
                    map.removeLayer(areaCircle);
                    areaCircle = null;
                }

                //  クリックされた地点を中心に円を描く
                areaCircle = L.circle([lat, lon],
                    {
                        radius: distance,
                        color: '#5555ff',
                        weight: 1,
                        fill: true,
                        opacity: 0.5
                    }
                );
                areaCircle.addTo(map);

                let features = data['resultsets']['features'];
                marker_list = [];
                let center = [lat, lon];
                let bounds = L.latLngBounds(center, center);    // 中心点で境界を初期設定する
                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 pos = [location[1], location[0]];
                    let marker = L.marker(pos);
                    marker.bindPopup(popup);
                    marker_list.push(marker);
                    bounds.extend(pos);    // 境界を拡張する
                }

                if (marker_list.length > 0) {
                    marker_layer = L.layerGroup(marker_list);
                    map.addLayer(marker_layer);
                }

                map.fitBounds(bounds);     // 地図表示を境界に合わせる
            });
        } 
    </script>

</head>

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

</html>

地図をクリックすると、見つかったデータに応じて、地図の表示位置と縮尺を自動的に調整し、すべての検索結果が画面に表示されるようになりました。

ひらの