表題の通り、GoogleMapAPIを用いた地点検索/経路検索/コワーキングスペース検索が出来る簡易Webアプリを作ったので記録します。
##目的
・JavaScriptの練習
・地図表示/マーカー表示の練習
・社内MBO(目標達成するとボーナス上がるやつ)に設定したので←
#####Webサイト
https://mapapps2021.azurewebsites.net/
※テストデータの都合上、コワーキングスペース検索では新宿あたりのコワーキングスペースしか出ません。
※簡単にレスポンシブ対応もしてます
なお、例えばですが、作りこむと下記のようなサービスになることを想定して作ってます。(ドトールの店舗検索サービス)
https://shop.doutor.co.jp/?_gl=1*1r4f95x*_ga*ODU2NTg5OTY3LjE2MTc0MzMwNDc.*_ga_XY2GKX2DP6*MTYxOTY1NTI1Ni4xMS4wLjE2MTk2NTUyNTYuMA..
#####ソースはこちら
アプリ本体(.NET Core + JavaScript)
コワーキングスペース検索で呼んでるAPI(.NET Core)
#####GoogleMapAPI導入方法
GCPのコンソールからAPIKeyを取得して、scriptタグをHTMLに追加しないといけなかったりします。こちらを参照してください。
##機能1 地点検索
検索欄に地名を入力し「Get Map」ボタンを押すと、入力した地点を中心とした地図が表示され、中心にマーカーが表示されます。
GoogleMapAPIの、地名と緯度経度の変換をしてくれる機能(Geocoding)が結構優秀なので、かなりマイナーな地名や、逆にざっくりしすぎた地名を入力しても検索してくれます。
####実装
ボタンのidを"getMap"とし、クリック時にjs記載の処理が走るようにしています。
<div class="heading font-weight-bold">地点検索</div>
<input class="width-full" id="address" type="text" placeholder="地点を入力してください" />
<button id="getMap" class="btn">Get Map</button>
// 地点検索 地点⇔座標変換
$('#getMap').click(function () {
// 画面入力値を取得
const address = document.getElementById("address")
.value;
// mapインスタンスを作る
const map = new google.maps.Map(document.getElementById("map"), {
zoom: 15
});
// GeocodingAPI使用 地点名(address)を緯度経度(results)に変換
const geocoder = new google.maps.Geocoder();
geocoder.geocode({ address: address }, function (results, status) {
if (status === google.maps.GeocoderStatus.OK) {
// 変換に成功したら
// あらかじめ作ったmapインスタンスの中心緯度経度を設定
map.setCenter(results[0].geometry.location);
// 中心にマーカーを設定
new google.maps.Marker({
map: map,
position: results[0].geometry.location,
});
}
else {
//変換に失敗したらアラート
alert("Geocode was not successful for the following reason: " + status);
}
});
})
##機能2 経路検索
始点、終点を入力し、移動方法を選択したうえで「Get Route」ボタンを押すと、始点をA、終点をBとした移動経路が記載されます。
経路検索にはGoogleMapAPIの機能の内の一つ「Direction API」を使ってます。
####実装
<div class="heading font-weight-bold">経路検索</div>
<div>
<input class="width-full" id="start" type="text" placeholder="始点を入力してください" />
</div>
<div>
<input class="width-full" id="end" type="text" placeholder="終点を入力してください" />
</div>
<div>
移動方法
<select id="travelmode">
<option value="DRIVING">車</option>
<option value="WALKING">徒歩</option>
<option value="BICYCLING">自転車</option>
<option value="TRANSIT">電車</option>
</select>
</div>
<button id="getRoute" class="btn" value="Get Route">Get Route</button>
// 経路検索
('#getRoute').click(function () {
//Direction用インスタンス生成
const directionsService = new google.maps.DirectionsService();
const directionsRenderer = new google.maps.DirectionsRenderer();
//mapインスタンス生成
const map = new google.maps.Map(document.getElementById("map"), {
zoom: 15
});
//画面入力値(始点・終点・モード)取得
const start = document.getElementById("start").value;
const end = document.getElementById("end").value;
const travelMode = document.getElementById("travelmode").value;
//経路表示線(polyline)の色を紫に変える
directionsRenderer.setOptions({
polylineOptions: {
strokeColor: '#6F51A1'
}
});
//画面入力値をdirectionServiceに教える
directionsService.route({
origin: start,
destination: end,
travelMode: google.maps.TravelMode[travelMode]
}, function (response, status) {
if (status === google.maps.DirectionsStatus.OK) {
//成功したらmapに表示
directionsRenderer.setMap(map);
directionsRenderer.setDirections(response);
}
else {
//失敗したらアラート
window.alert("Directions request failed due to " + status);
}
});
})
##機能3 コワーキングスペース検索
「コワーキングスペース表示」ボタンを押すと、別途作った自作APIを呼び、呼び出し結果を加工して地図にマーカー表示する。
また、呼び出し結果のうち最初の3件を画面右側に詳細表示する。
####実装
別途作ったコワーキングスペース検索API(get通信叩くとDBからコワーキングスペース情報をJSONで返す)をたたいて、ajaxで地図表示しています。
<div class="heading font-weight-bold">近隣コワーキングスペース検索</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="currentpositionflg">
<label class="form-check-label" for="currentpositionflg">現在地周辺を検索</label>
</div>
<button id="getCW" class="btn">コワーキングスペース表示</button>
$(function () {
$('#getCW').click(
function () {
$.ajax({
//自作 コワーキングスペースAPI
url: 'https://coworkingspaceapi.azurewebsites.net/Coworkingspace',
//通信方法
type: 'GET',
//データ形式を指定
dataType: 'json',
//通信に成功した場合の処理
}).done(function (data) {
// データを地図外に表示
displayHtml(data);
const useCurrentPosition = document.getElementById("currentpositionflg");
if (useCurrentPosition.checked) {
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(
function (position) {
const currentLatLng = new google.maps.LatLng(
position.coords.latitude, position.coords.longitude);
displayMapByCurrentPosition(data, currentLatLng);
},
function (error) {
switch (error.code) {
case 1: // PERMISSION_DENIED
alert("位置情報の利用が許可されていません");
break;
case 2: // POSITION_UNAVAILABLE
alert("現在位置が取得できませんでした");
break;
case 3: // TIMEOUT
alert("タイムアウトになりました");
break;
default:
alert("その他のエラー(エラーコード:" + error.code + ")");
break;
}
}
)
} else {
alert("この端末では位置情報が取得できません");
}
} else {
displayMap(data);
}
//通信エラーになった場合の処理
}).fail(function (data) {
alert("失敗しましたm(__)m");
});
});
});
function displayMap(data) {
const map = new google.maps.Map(document.getElementById("map"), {
zoom: 15,
// 仮の値(東京都庁)
center: { lat: 35.6896385, lng: 139.689912 }
});
//現在地表示
const center = map.getCenter();
makeCurrentPos(map, center.lat(), center.lng());
//データを距離によって選択
var arrayPlace = [];
var arrayName = [];
for (let i = 0; i < data.length; i++) {
const place = new google.maps.LatLng(data[i].lat, data[i].lon);
const distance = google.maps.geometry.spherical.computeDistanceBetween(center, place);
if (distance < 2400) {
arrayPlace.push(place);
arrayName.push(data[i].name);
}
}
// 地図範囲調整
// 検索結果によっては地図が見づらくなることがあるので...
map.fitBounds(new google.maps.LatLngBounds(
// sw
{
lat: Math.min(...arrayPlace.map(d => d.lat())),
lng: Math.min(...arrayPlace.map(d => d.lng()))
},
// ne
{
lat: Math.max(...arrayPlace.map(d => d.lat())),
lng: Math.max(...arrayPlace.map(d => d.lng()))
}
));
const markers = Array(arrayPlace.length);
const infoWindows = Array(arrayPlace.length);
for (let i = 0; i < arrayPlace.length; i++) {
const cwLat = arrayPlace[i].lat();
const cwLng = arrayPlace[i].lng();
markers[i] = new google.maps.Marker({
map: map,
position: { lat: cwLat, lng: cwLng }
});
// 吹き出しの追加
infoWindows[i] = new google.maps.InfoWindow({
content: '<div class="map">' + arrayName[i] + '</div>'
});
// マーカークリック時の吹き出し表示
markers[i].addListener('click', function () {
infoWindows[i].open(map, markers[i]);
});
}
}
function displayMapByCurrentPosition(data, currentLatLng) {
var map = new google.maps.Map(document.getElementById("map"), {
zoom: 15,
// 現在地
center: currentLatLng
});
//現在地表示
const center = map.getCenter();
makeCurrentPos(map, center.lat(), center.lng());
//データを距離によって選択
var arrayPlace = [];
var arrayName = [];
for (let i = 0; i < data.length; i++) {
const place = new google.maps.LatLng(data[i].lat, data[i].lon);
const distance = google.maps.geometry.spherical.computeDistanceBetween(center, place);
if (distance < 2400) {
arrayPlace.push(place);
arrayName.push(data[i].name);
}
}
// 地図範囲調整
map.fitBounds(new google.maps.LatLngBounds(
// sw
{
lat: Math.min(...arrayPlace.map(d => d.lat())),
lng: Math.min(...arrayPlace.map(d => d.lng()))
},
// ne
{
lat: Math.max(...arrayPlace.map(d => d.lat())),
lng: Math.max(...arrayPlace.map(d => d.lng()))
}
));
const markers = Array(arrayPlace.length);
const infoWindows = Array(arrayPlace.length);
for (let i = 0; i < arrayPlace.length; i++) {
const cwLat = arrayPlace[i].lat();
const cwLng = arrayPlace[i].lng();
markers[i] = new google.maps.Marker({
map: map,
position: { lat: cwLat, lng: cwLng }
});
// 吹き出しの追加
infoWindows[i] = new google.maps.InfoWindow({
content: '<div class="map">' + arrayName[i] + '</div>'
});
// マーカークリック時の吹き出し表示
markers[i].addListener('click', function () {
infoWindows[i].open(map, markers[i]);
});
}
}
// 現在地マーカー作成
function makeCurrentPos(map, currentLat, currentLon) {
const currentMarker = new google.maps.Marker({
map: map,
position: { lat: currentLat, lng: currentLon },
icon: {
url: 'https://maps.google.com/mapfiles/ms/icons/purple-dot.png',
scaledSize: new google.maps.Size(40, 40)
}
})
const currentMarkerWindow = new google.maps.InfoWindow({
content: '<div class="map">現在地</div>'
});
// マーカークリック時の吹き出し表示
currentMarker.addListener('click', function () {
currentMarkerWindow.open(map, currentMarker);
});
};
//APIの戻り値最初の3件をHTML表示
function displayHtml(data) {
$('#cw1_name').html(data[0].name);
$('#cw1_description').html(data[0].description);
$('#cw1_link').html("公式サイト");
var target1 = document.getElementById("cw1_link");
target1.href = data[0].url;
$('#cw2_name').html(data[1].name);
$('#cw2_description').html(data[1].description);
$('#cw2_link').html("公式サイト");
var target2 = document.getElementById("cw2_link");
target2.href = data[1].url;
$('#cw3_name').html(data[2].name);
$('#cw3_description').html(data[2].description);
$('#cw3_link').html("公式サイト");
var target3 = document.getElementById("cw3_link");
target3.href = data[2].url;
}
[
{
"cw_id": 1,
"name": "HAKADORU 新宿三丁目店",
"lon": 139.70319,
"lat": 35.690205,
"url": "https://haka-doru.jp/",
"description": "〒160-0022 東京都新宿区新宿3丁目1−24 京王新宿三丁目ビル 1F",
"area_id": 1,
"station_id": 1,
"has_FreeDrink": true,
"is_RecentCreated": false,
"created": "2021-02-03T00:00:00",
"updated": "2021-02-03T00:00:00"
},
{
"cw_id": 2,
"name": "Workmedi",
"lon": 139.6952,
"lat": 35.697464,
"url": "https://www.workmedi.jp/",
"description": "〒160-0023 東京都新宿区西新宿7丁目7−26 ワコーレ新宿第一ビル 2階",
"area_id": 1,
"station_id": 1,
"has_FreeDrink": true,
"is_RecentCreated": false,
"created": "2021-02-03T00:00:00",
"updated": "2021-02-03T00:00:00"
},
{
"cw_id": 3,
"name": "INBOUND LEAGUE",
"lon": 139.69907,
"lat": 35.692673,
"url": "https://inbound-league.jp/",
"description": "〒160-0022 東京都新宿区新宿5丁目15−14",
"area_id": 1,
"station_id": 1,
"has_FreeDrink": true,
"is_RecentCreated": false,
"created": "2021-02-03T00:00:00",
"updated": "2021-02-04T00:00:00"
},
{
"cw_id": 4,
"name": "勉強カフェ",
"lon": 139.69453,
"lat": 35.68686,
"url": "https://www.benkyo-cafe.net/studio/shinjuku/",
"description": "〒151-0053 東京都渋谷区代々木2丁目14−1 新宿松本ビル6F",
"area_id": 1,
"station_id": 1,
"has_FreeDrink": true,
"is_RecentCreated": false,
"created": "2021-02-03T00:00:00",
"updated": "2021-02-04T00:00:00"
},
{
"cw_id": 5,
"name": "クロスオフィス新宿",
"lon": 139.69681,
"lat": 35.694733,
"url": "https://www.crossoffice.jp/office/shinjuku/",
"description": "〒160-0023 東京都新宿区西新宿7丁目1−12",
"area_id": 1,
"station_id": 1,
"has_FreeDrink": true,
"is_RecentCreated": false,
"created": "2021-02-03T00:00:00",
"updated": "2021-02-04T00:00:00"
},
{
"cw_id": 7,
"name": "TSUTAYA BOOK APARTMENT",
"lon": 139.70016,
"lat": 35.69179,
"url": "http://tsutaya.tsite.jp/feature/store/tba_shinjuku/",
"description": "〒160-0022 東京都新宿区新宿3-26-14 新宿ミニムビルⅠ4F ~6F、新宿ミニムビルⅡ5F~6F",
"area_id": 1,
"station_id": 1,
"has_FreeDrink": true,
"is_RecentCreated": false,
"created": "2021-02-01T00:00:00",
"updated": "2021-02-01T00:00:00"
},
{
"cw_id": 10,
"name": "moboff SHINJUKU i-LAND",
"lon": 139.69095,
"lat": 35.69313,
"url": "http://www.moboff-shinjuku.jp/",
"description": "〒163-1320 東京都新宿区西新宿6丁目5−1 新宿アイランドタワ 20階",
"area_id": 1,
"station_id": 1,
"has_FreeDrink": true,
"is_RecentCreated": false,
"created": "2021-02-01T00:00:00",
"updated": "2021-02-01T00:00:00"
},
{
"cw_id": 11,
"name": "BIZcomfort(ビズコンフォート)田町",
"lon": 139.74352,
"lat": 35.64858,
"url": "https://bizcomfort.jp/",
"description": "〒105-0014 東京都港区芝5丁目16−14 三田ノックスビル",
"area_id": 1,
"station_id": 1,
"has_FreeDrink": true,
"is_RecentCreated": false,
"created": "2021-02-11T00:00:00",
"updated": "2021-02-11T00:00:00"
},
{
"cw_id": 12,
"name": "BASE POINT",
"lon": 139.69307,
"lat": 35.69512,
"url": "http://office-bp.jp/",
"description": "〒160-0023 東京都新宿区西新宿7丁目22−3 BP ビル ANNEX",
"area_id": 1,
"station_id": 1,
"has_FreeDrink": true,
"is_RecentCreated": false,
"created": "2021-02-11T00:00:00",
"updated": "2021-02-11T00:00:00"
},
{
"cw_id": 14,
"name": "HAPON SHINJUKU",
"lon": 139.69641,
"lat": 35.696007,
"url": "https://hapon.asia/shinjuku/",
"description": "〒160-0023 東京都新宿区西新宿7丁目4−4",
"area_id": 1,
"station_id": 1,
"has_FreeDrink": true,
"is_RecentCreated": false,
"created": "2021-02-11T00:00:00",
"updated": "2021-02-11T00:00:00"
}
]
##【補足】イケてないところ、改善点
・地図表示画面が小さい。本家GoogleMapみたいに地図表示の割合を大きくして、その上に検索欄を作るなどしたい
・コワーキングスペース検索で、地図上の検索結果と右側に出る詳細情報が紐づいてなくて見づらい
長文ですが、長くなってしまいましたね。
機能ごとに分けて記事を詳細化するかもしれません。