BODIK APIの使い方(Google編)

BODIK APIの使い方(Javascript編)」では、アプリに近い形として、JavascriptからBODIK APIの呼び出し方を学びました。

このときは、「どのようにしてJavascriptからBODIK APIを呼び出すのか?」を理解するために、Javascriptを記述したHTMLファイルを直接ブラウザに呼び出すことで、ローカルで機能を確認しました。実際にアプリケーションとして使う場合は、何らかのサーバーが欲しくなります。しかし、専用のサーバーを構築することを考えると、難易度が上がってしまいます。どうにかならないか?

Googleの「Apps Script」を使えばこれを実現できます。

Google Apps Scriptを使ったWebアプリの作成

「BODIK APIの使い方(Javascript編)」の「課題2.地図に表示する」に追加機能を組み込んだHTMLを例にして、Webアプリを作ってみましょう。

<!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>

Googleスプレッドシートを用意する

  1. ご自分のGoogleドライブの任意のフォルダにGoogleスプレッドシートを作成してください。
  2. メニューの「拡張機能」から「Apps Script」を実行すると、「Apps Script」の画面が表示されます。

アプリの入口を作成する

  1. 初期表示されている「function myFunction()」を全部消して、次のコードに書き換える。
function doGet(e) {
  let template = HtmlService.createTemplateFromFile("index");
  return template.evaluate();
}

HTMLファイルを作成する

  1. 左側の「ファイル」の右にある「+記号(ファイルの追加)」をクリックし、「HTML」を選択する。
  2. 追加されたhtmlファイルの名前を「index」に変更する。
  3. 「BODIK APIの使い方」のHTMLファイルから、「body」の部分をクリップボードにコピーする。
  4. index.htmlの「body」部分をクリップボードからペーストする。
  5. 地図を表示するためのLeafletライブラリの宣言部分をコピペする。
<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
    <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>
  </head>
  <body onload="init()">
    <h2>BODIK API program #2</h2>
    <div class="map" id="map" style="width:800px;height:600px"></div>
  </body>
</html>

Javascript部分を組み込む

  1. 左側の「ファイル」の右にある「+記号(ファイルの追加)」をクリックし、「HTML」を選択する。
  2. 追加されたhtmlファイルの名前を「js」に変更する。
  3. 「BODIK APIの使い方」のHTMLファイルからBODIK APIを呼び出しているjavascript部分(<script>から</script>まで)をクリップボードにコピーする。
  4. js.htmlファイルの内容をクリップボードからペーストして置き換える。
    <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>

HTMLにJavascriptを追加する

  1. index.htmlにJavascriptファイルを読み込むために次の行を追加する。
<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
    <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>
    <?!= HtmlService.createHtmlOutputFromFile('js').getContent(); ?>
  </head>
  <body onload="init()">
    <h2>BODIK API program #2</h2>
    <div class="map" id="map" style="width:800px;height:600px"></div>
  </body>
</html>

プロジェクトをデプロイする

以上で、HTMLファイルで記述したBODIK API呼び出しのサンプルプログラムを、コピペでApps Scriptに組み込むことができました。これをWebアプリとして発行しましょう。

  1. Apps Scriptの画面で、「デプロイ」ボタンを押し、「新しいデプロイ」を選択する。
  2. 初めてのデプロイの場合、「種類の選択」の「歯車アイコン」をクリックし、「ウェブアプリ」を選択する。
  3. 自分だけが使う場合は、「自分のみ」になっていることを確認し、「デプロイ」ボタンを押す。
  4. 「ウェブアプリ」のURLを「コピー」し、「完了」ボタンを押す。
  5. ブラウザを表示し、URL部分にコピーした「ウェブアプリのURL」を貼り付けて、表示する。
    • ウェブアプリのURLは非常に長い文字列となっている。

まとめ

HTMLファイルで確認したコードを、Webアプリとして構築することができました。デプロイ時に「自分のみ」の利用としているので、「ウェブアプリのURL」を呼び出したとき、Googleがユーザー認証してくれます。

今回は「Googleスプレッドシート」のApps Scriptに組み込みました。Googleスプレッドシートの機能は使っていませんので、単純に「Apps Script」として作成することも可能です。

「Googleスプレッドシート」に組み込むことで、今後の機能拡張としてGoogleスプレッドシートをデータベースのように利用することも可能です。

「GAS HTML」などのキーワードで検索すると、いろいろな事例が見つかります。ぜひ、いろいろ試してください。

ひらの