BODIK APIを使ったJavascriptプログラムを書いてみます。
課題2:BODIK APIの検索結果を地図に表示する
問題:BODIK APIを使って、AEDを検索し、結果を地図に表示(マーカーを表示)する。
基本情報
- Javascriptで地図を表示する場合、「Leaflet」というライブラリを使うと便利です。
- JavascriptでLeafletを使うためには、ライブラリをインポートする必要があります。
- Leafletにはいくつかの関数が用意されています。詳細は公式HPをご参照ください。
Leaflet : https://leafletjs.com/
| Leaflet関数 | 機能 | 例 |
|---|---|---|
| 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>
お疲れ様でした。
