BODIK APIを使ったJavascriptプログラムを書いてみます。
課題2:BODIK APIの検索結果を地図に表示する
問題:BODIK APIを使って、AEDを検索し、結果を地図に表示(マーカーを表示)する。
基本情報
- Javascriptで地図を表示する場合、「Leaflet」というライブラリを使うと便利です。
- JavascriptでLeafletを使うためには、ライブラリをインポートする必要があります。
- Leafletにはいくつかの関数が用意されています。詳細は公式HPをご参照ください。
Leaflet : https://leafletjs.com/
folium関数 | 機能 | 例 |
---|---|---|
L.map() | マップを作成する | let map = L.map('map'); |
L.popup() | ポップアップを作成する | let popup = L.popup() |
L.marker() | マーカーを作成する | let marker = L.marker(location).add_to(map); |
swaggerで試す
最初に、WAPIのswagger(https://wapi.bodik.jp/docs)でAPIを試してみましょう。
AED(GET /aed)のエンドポイントを表示し、「try it out」のボタンを押します。
今回は位置情報が欲しいので、いくつかのパラメータを指定します。

下の方にある青い「Execute」ボタンを押します。
検索結果が表示されます。「Request URL」のところを確認します。

パラメータ部分が変わりました。 select_type=geometry maxResults=10 lat=33.59328962901721 lon=130.35598920962553 distance=2000
Javascriptで記述
最初に、Javascriptで「BODIK API」を呼び出して位置情報を取得してみましょう。
緯度と経度を指定して、位置情報で検索します。
位置情報は、featureの「geometry」に格納されています。
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>BODIK API</title> <script type="text/javascript"> let api_server = 'https://wapi.bodik.jp'; let api = 'aed'; let lat = 33.59334325082392; let lon = 130.35598920962553; let distance = 2000; let api_url = `${api_server}/${api}?select_type=geometry&lat=${lat}&lon=${lon}&distance=${distance}`; fetch(api_url) .then(response => response.json()) .then(data => { let array = []; for (let feature of data['resultsets']['features']) { array.push(feature); } let output = document.getElementById('output'); output.innerHTML = JSON.stringify(array, null, 2); }); </script> </head> <body> <h2>BODIK API program #2</h2> <pre id="output"></pre> </body> </html>
featureのgeometryは、次のような構造になっています。
'geometry' : { 'type': 'Point', 'coordinates': [ lon, lat ] # 緯度(lat)と経度(lon)が逆になっていることに注意する }
検索結果のgeometryを使って、地図に表示すればいいのですが、Javascriptで地図を表示するにはどうしたらいいのでしょうか?
Javascriptで地図を表示
Javascriptで地図を表示する方法として、Leafletが便利です。
Leafletを使うためには、LeafletのCSSとライブラリを参照します。
<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>
とりあえず、地図を表示してみましょう。
<!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; 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); } </script> </head> <body onload="init()"> <h2>BODIK API program #2</h2> <div class="map" id="map" style="width:800px;height:600px"></div> </body> </html>
地図にマーカーを立てる
地図の指定した場所にマーカーを立てる方法を調べます。
// lat, lonで指定された場所にマーカーを作成する let marker = L.marker([lat, lon]).addTo(map);
マーカーをクリックしたときにポップアップを表示したい。
let text = 'ポップアップに表示する文字列'; let popup = L.popup().setContent(text); let marker = L.marker([lat, lon]).addTo(map); marker.bindPopup(popup);
BODIK APIと組み合わせる
BODIK APIの検索結果から位置情報を取り出し、地図にマーカーを立ててみましょう。
<!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; 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); let api_server = 'https://wapi.bodik.jp'; let api = 'aed'; let distance = 2000; let api_url = `${api_server}/${api}?select_type=geometry&lat=${lat}&lon=${lon}&distance=${distance}`; fetch(api_url) .then(response => response.json()) .then(data => { let features = data['resultsets']['features']; 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); } }); } </script> </head> <body onload="init()"> <h2>BODIK API program #2</h2> <div class="map" id="map" style="width:800px;height:600px"></div> </body> </html>
マウスクリック処理
地図をクリックされた場所で検索できるようにしてみましょう。
まずは、マーカーを立てる部分を関数にします。
<!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; 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); } function showMarker(lat, lon) { let api_server = 'https://wapi.bodik.jp'; let api = 'aed'; let distance = 2000; let api_url = `${api_server}/${api}?select_type=geometry&lat=${lat}&lon=${lon}&distance=${distance}`; fetch(api_url) .then(response => response.json()) .then(data => { let features = data['resultsets']['features']; 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); } }); } </script> </head> <body onload="init()"> <h2>BODIK API program #2</h2> <div class="map" id="map" style="width:800px;height:600px"></div> </body> </html>
次に、マウスのクリックハンドラを組み込みます。
任意の場所がクリックされるので、検索範囲を広げましょう。
distance = 20000, maxResults = 100に変更する
<!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; 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 => { let features = data['resultsets']['features']; 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); } }); } </script> </head> <body onload="init()"> <h2>BODIK API program #2</h2> <div class="map" id="map" style="width:800px;height:600px"></div> </body> </html>
なんとなく、アプリっぽくなりましたね。Leafletの地図はマウスを使って、(ホイールで)拡大縮小や(ドラッグで)スクロールすることができます。いろいろなところに場所を移動させて試してみてください。
この状態ですと、地図をクリックするたびに、マーカーが追加されてしまうという欠点があります。この対策としては、
- マーカーを作成したときに記録しておき、
- 次にクリックされたとき、前回表示したマーカーを削除する
という対応が必要です。応用編として挑戦してみてください。
プログラム(完成版)
エラー処理を入れて完成です。
<!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; function init() { try { 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); } catch(error) { alert('init:' + error); } } function onMapClick(e) { try { let latlng = e.latlng; let lat = latlng.lat; let lon = latlng.lng; showMarker(lat, lon); } catch(error) { alert('onMapClick:' + error); } } function showMarker(lat, lon) { try { 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 ('resultsets' in data) { let resultsets = data['resultsets']; if ('features' in resultsets) { let features = resultsets['features']; 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); } } else { alert('no "features" in data'); } } else { alert('no "resultsets" in data'); } }); } catch(error) { alert('showMarker:' + error); } } </script> </head> <body onload="init()"> <h2>BODIK API program #2</h2> <div class="map" id="map" style="width:800px;height:600px"></div> </body> </html>
お疲れ様でした。