チュートリアル google map javascript

【デモあり】Google Maps APIを使った、オリジナルマップのメリットと実装方法

こんにちは、アクトゼロ・マークアップエンジニアの小林です。
以前はデザイナーとして本ブログで記事を投稿したりしていましたが、コーディングもしたくなりエンジニアにジョブチェンジしました。

エンジニアになってから早くも1年半が経とうとしています。激動のフロントエンド界隈でも長く戦っていけるように、JavaScript等の基礎を学んでいますが、あれもこれもやってみたい衝動に駆られてヤキモキしているところです(汗)。

さて、今月からアクトゼロ・エンジニアのメンバーもブログを執筆することになりました(以前、エンジニアも執筆していたので正しくは「再開」)。

今回は、Google マップ(Google Maps API)を使ったオリジナルマップについてご紹介したいと思います。
実際にデモを作りましたのでその実装方法についてもご紹介します。

デモページを見る

非エンジニアの方でも、本記事を通して「Google マップを使えばこのようなこともできるんだ」と知って頂くことができたら幸いです。

※この記事は2016/10/6時点でのGoogle Maps APIを利用しています。今後、APIの仕様が変わり正しく動作しなくなる可能性もあります。あらかじめご了承下さい。

 

Google マップを上手く使った企業サイト

百聞は一見に如かず、まずは実際にGoogle マップを使った企業サイトを見てみましょう。

スターバックス コーヒー ジャパン

map_02

http://www.starbucks.co.jp/store/search/result.php?search_type=1&pref_code=13

仕様概要

  • 左上のプルダウンから選択した都道府県の店舗が絞り込まれて表示される。
  • 左の店舗リストから特定の店舗をクリックすると、マップ上でその店舗がフォーカスされる。
  • マップ上で★の店舗マーカーをマウスオーバーすると、その店舗名がツールチップ(吹き出し)のようなカタチで表示される。
  • 店舗マーカーをクリックすると店舗詳細ページへ遷移する。

非常に多くの店舗を持つスターバックス ジャパンの店舗検索ページです。
東京のように300店舗近い場合、テキストだけで表示すると見にくい&探すのが大変そうです。
しかしマップ上に表示することで、現在地からの最寄りの店舗はどこか、そしてそこまでの距離感がわかったり、一覧で探しやすいというメリットがあります。

 

日本マクドナルド

map_03

https://map.mcdonalds.co.jp/

仕様概要

  • 現在地取得機能がある。
  • 地名・駅名検索では、入力した地域にフォーカスされる(地図が動くだけ)。
  • 店名検索では、入力した文字を含む店舗が絞り込まれる。
  • 絞り込み検索で、細かな条件検索が可能。
  • マップ上のマーカーをクリックすると現れる店舗情報の吹き出しの中に、絞り込み用のアイコンがある。

基本的には、店舗数が同じく多かったスターバックスのページと似ています。ただこちらでは、左側にある検索機能が充実していると感じます。
特にFREE WiFiや24時間営業等の条件検索は、ユーザーからしたらかなり有用な情報です。
そして現在地取得機能があり、取得に成功すると最寄の店舗が見つけやすくなります。
また、マーカーも大きくてマップとの色のコンストラクトも大きいので目立っていて見やすいです。

 

ベネッセ コーポレーション

map_04

https://www.benesse.co.jp/store-search/

仕様概要

  • 施設の種類によってマーカーの色が異なっている。
  • 検索の仕方が3種類もある(「地域から探す」「目的から探す」「サービスから探す」)。
  • 現在地取得機能がある。
  • デフォルトの状態(初期画面)でマップをドラッグすると、そのマップ内に存在するマーカーが自動で表示される(マップ外のマーカーは消える)。
  • マップ上のマーカーをクリックすると、自動でズーム表示になる。
  • 左の店舗リストで特定の店舗をマウスオーバーすると、マップ上でその店舗の吹き出しが表示される。

ベネッセの店舗検索ページでは、異なるサービスの店舗もまとめて掲載しても見やすいように様々な工夫がされています。
そのひとつが、マップ上部にある検索機能で、地域だけでなく様々な目的・サービスから選ぶことができます。
それに伴い、マップ上のマーカーもキレイに色分けされていて直感的に探しやすくなっています。
様々なサービスを展開しているベネッセならではの見せ方ではないでしょうか。

 

Google マップを使うメリット

このように見てみると、多量の店舗を表示させたい場合、パッと見で探しやすいというのが一番のメリットではないでしょうか。
また現在地取得機能があれば、移動中では大変便利だと思います。
普通の地図では、まず現在地を探すところから入るのでそれをスルーできるのは魅力です。

 

Webで使うのはAPIの中でもGoogle Maps JavaScript API

Google MapsはAPI(Google Maps API)を公開しており、これを用いることで、自分のサイトやアプリなどで、Google Mapsの機能を使った独自のコンテンツを作ることが可能です。

ただ、Google Maps APIと一言でいうものの実は機能別にたくさんの種類に分かれています。
一例をあげると下記のようなAPIがあります。

名称 概要
Google Maps Android API Android アプリ用。マップを追加できる。
Google Maps SDK for iOS iOS アプリ用。マップを追加できる。
Google Maps JavaScript API ウェブ用。独自のコンテンツと画像でマップをカスタマイズできる。
Google Street View Image API ウェブ用。実世界の画像とパノラマ画像を使用できる。
Google Maps Geocoding API ウェブサービス用。住所と地理的座標を変換できる。

この他にも様々なAPIがあります、詳しくは公式のGoogle Maps APIのページをご覧ください。

今回は一般的な、webサイトで利用するためのGoogle Maps JavaScript APIを用いて話を進めていきます。

 

デモページの紹介

前置きが長くなりましたが、Google Maps APIを使ったデモページをご紹介します。

デモページを見る

全国の地方自治体を上記3サイトにおける店舗と見立てて実装しました。
(上記3サイトと比べるとだいぶ機能が削ぎ落とされていますが。)

挙動の説明等はデモページ上に記載していますので、こちらでは具体的な実装方法について順番に説明していきたいと思います。
なお、実装にはHTML、CSS、JavaScriptとjQueryの基礎知識があれば大丈夫だと思います。

1.必要なデータを準備する

まず、マップ上に表示する建物のデータが必要です。今回の実装にあたってはエクセルで、下記のようなデータを作成しました。

05

B列の区分を作った理由は、都道府県庁とその他の役場では、マップ上のマーカー画像を変えて区別したいためです。

そしてG列の緯度と、H列の経度は、マーカーを置く座標として必要になります。
座標はどうやって取ったのかというと、便利なこちらのサイトを使わせて頂きました。
複数の住所から緯度経度を一括で取得する

こちらのサイトでE列の住所をまるっとコピペすると、下記のように座標を一行ずつ出力してくれます。(数が多いと少し時間がかかりますが気長に待ちましょう)

06

もしここで、間違った住所が入力されて出力ができなかった場合、その時点で出力が止まります。
その時は、その出力されなかった住所が本当に正しいか確認してみて下さい。正しい住所を入力すればきちんと出力されるはずです。

余談ですが、この出力されたデータの緯度と経度を一つ一つちまちまとコピペしていては日が暮れるので正規表現を使いましょう。
今回では下記のように ok,[^,]+,(.+),(.+) とすれば、$1で緯度、$2で経度に置換できます。

07

08

これをまるっとエクセルにコピペすれば、基本のデータ作成は完成です。

コラム:座標は最初に用意しなくても実装できる

実はGoogle Maps JavaScript APIの中で、住所から座標を取得するGeocoding(ジオコーディング)というサービスがあり、これを使えばわざわざ座標を用意する必要はありません。
しかし前述の通り、住所が正しくない場合、座標が上手く取れない場合があります。その場合、おそらくその住所だけマーカーが出なかったり、処理が止まってしまうことが考えられます。結局、最初に座標を用意するよりも対応が面倒になるかもしれません。
私は、最初に用意しておく情報が多ければ多いほど、後が楽になると考えています。また、JavaScriptの処理の負担も減らせるので最初から座標を用意することをおすすめします。
しかし頻繁にマーカーの追加などが行われる場合は、ジオコーディングを使ったほうが後々楽かもしれませんので、状況に応じて使い分けると良いと思います。

 
最後に、作成したデータはJavaScriptから扱いやすいjsonというデータ・フォーマットに整形します。(詳しくは後述しますがこのjsonデータをAjaxで取得してマーカーを配置するのです。)

エクセルからjsonへの変換はこちらのサイトを使わせて頂きました。
CSV→JSON変換

エクセルからは別名保存で簡単にcsvとして保存できますので、それをメモ帳などで開き、コピペして出力します。

09

1点気をつけて頂きたいのは、出力されたデータの先頭にある「{(波括弧)」と末尾の「};」は削除してください。「[(角括弧)」ではじまり「]」で終わるようにします。でないとajaxで取得できなくなります。
あとは適当な名前で拡張子を.jsonで保存(今回はdata.jsonとして保存)すればデータの完成です!

データの作成がバッチリできればもう半分できたようなものです。
このデータの作成が最初はとても大変でした…。

 

2.Google Maps JavaScript APIキーを取得する

次にAPIキーを取得します。取得の仕方は公式のガイド(日本語)が丁寧に説明してくれていますが、ざっと紹介いたします。

まずは公式ガイドページから「キーの取得」ボタンをクリックします。

08

次に表示された画面で「プロジェクトを作成」がプルダウンで表示されている状態で「続行」をクリックします。プロジェクトが作成されるまで少し待機します。

07

プロジェクトが作成されると認証情報を入力する画面になります。

  • 「APIキーの名前」は好きな名前を入力します(デフォルトでも問題ありません)。
  • 「キーの制限」では、今回のようにwebサイトで使うのであれば「HTTPリファラー(ウェブサイト)」を選択します。
  • 「このHTTPリファラー(ウェブサイト)からのリクエストを受け入れる」の箇所は、公開する際には必ず公開するドメイン名を含んだリファラーを設定します。
    APIキーの値自体はhtmlファイル上から見えるので、悪意ある人が使用する可能性があります。もしこのリファラーがワイルドカード(*)になっていると、すべてのサイトからリクエストが有効になり、気付いたらとんでも額の請求がきていた!なんてこともあり得ます(利用制限を守っていれば無料で使えます。この使用制限に関しては後述します)。
    なお、サンプルのように「*.sample.com/*」と指定すると、sample.com配下はもちろん、サブドメインに対しても有効になります。
  • 最後に、「作成」ボタンをクリックします。

その後、APIキーが作成されると下記モーダル画面が表示されます。

10

このキーを下記のようにkeyパラメータに記述すればOKです。


<script src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY"></script>

 

3.HTMLを書く

HTMLは下記のようにしました。余分なところは抜いていますので、全文を見たい方はデモページから右クリック → 「ページのソースを表示」から御覧ください。

  <!-- ナビ部分ここから -->
  <nav class="nav" id="js-nav">
    <button class="btn" id="js-btnGetPosition">→ 現在地を取得</button>
    <div><a href="#map">全国(初期表示)</a></div>
    <dl>
      <dt>北海道地方</dt>
      <dd><a href="#map" data-lat="43.471853" data-lng="142.791802" data-zoom="7">北海道</a></dd>
    </dl>
    <dl>
      <dt>東北地方</dt>
      <dd><a href="#map" data-lat="41.012599" data-lng="140.800008" data-zoom="9">青森県</a></dd>
      <dd><a href="#map" data-lat="39.607986" data-lng="141.424491" data-zoom="8">岩手県</a></dd>
      <dd><a href="#map" data-lat="38.337695" data-lng="140.971756" data-zoom="9">宮城県</a></dd>
      <dd><a href="#map" data-lat="39.776347" data-lng="140.452517" data-zoom="8">秋田県</a></dd>
      <dd><a href="#map" data-lat="38.473696" data-lng="140.144503" data-zoom="8">山形県</a></dd>
      <dd><a href="#map" data-lat="37.436669" data-lng="140.205007" data-zoom="9">福島県</a></dd>
    </dl>
    ...(略)...
  </nav>
  <!-- ナビ部分ここまで -->

  ...(略)...

  <div class="map" id="js-map"><!-- ここにマップを生成します --></div>
  <div class="message" id="js-message">マーカーをクリックして下さい</div>
  <div class="infoBox is-hidden" id="js-info"><!-- ここにクリックした役場の詳細情報を表示します --></div>
  <table class="otherTable is-hidden" id="js-other">
    <caption>同じ都道府県にある店舗</caption>
    <thead>
      <tr>
        <th>区分</th>
        <th>名称</th>
        <th>郵便番号</th>
        <th>住所</th>
        <th>電話番号</th>
      </tr>
    </thead>
    <tbody>
    <!-- ここに関連する役場の情報を表示します -->
    </tbody>
  </table>

  ...(略)...

<script src="https://maps.googleapis.com/maps/api/js?key=取得したAPIキー"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
<script src="js/local.js"></script>
</body>

ここでのポイントは、下記になります。

  • 2~19行目:nav要素の中で、都道府県のaタグにカスタムデータ属性で3種類の値を指定。
  • 24行目:マップを表示するための空のdiv要素を作っておく。
  • 26行目・27行目:クリックしたら情報を出すブロックにis-hiddenクラスを付けて非表示にしておく。(is-hiddenクラスにはdisplay:none;を指定)
  • 45行目:Google Maps APIキーを読み込む

 

4.CSSを書く

続いてCSSです。重要なのは、マップが表示されるエリアの大きさをしっかりと確保しておくことです。

/* マップの大きさを確保しておく*/
.map {
  width: 100%;
  height: 500px;
  ...(略)...
}

その他のCSSはほぼレイアウトに関することなので割愛させて頂きます。

 

5.JavaScriptを書く

いよいよJavaScriptを書いていきます。下記が全文になります。

/*=====================================
* 変数の定義
=====================================*/
var map; // マップのインスタンスを格納する変数

var initOption = { // マップ初期表示用のオプション
	latLng: {
		lat: 35.689634,
		lng: 135.692101
	},
	zoom: 4
};
var getPositionBtn = document.getElementById('js-btnGetPosition'); // 現在地取得ボタン
var messageTxt = document.getElementById('js-message');            // クリック訴求のメッセージ
var infoBox = document.getElementById('js-info');                  // クリックした役場の詳細情報を表示するエリア
var otherTable = document.getElementById('js-other');              // 同じ都道府県にあるその他役場を表示するテーブル
var otherTableTbody = document.querySelector('#js-other tbody');   // 上記テーブルのtbody

var markerNowPosition = null;                         // 現在地を示すマーカー
var jsonData;                                         // 取得したjsonデータ
var markersArray = [];                                // マーカーの配列
var navList = document.querySelectorAll('#js-nav a'); // ナビ
var newClearMarkersArray = [];                        // これから非表示にするマーカーの配列
var currentClearMarkersArray = [];                    // 非表示になっているマーカーの配列
var infoWindow;                                       // 店舗名を表示する吹き出し

var CLASS_HIDDEN = 'is-hidden'; // 非表示用のクラス名

var newAreaName;     // クリックしたナビのテキスト
var currentAreaName; // 比較するための変数


/*=====================================
* イベントの設定
=====================================*/

// ページの読み込みが完了後
window.addEventListener('load', function () {
	initMap();
});

// ナビをクリック時
for (var i = 0; i < navList.length; i++) {
	navList[i].addEventListener('click', function (e) {
		// デフォルトのイベントを無効化
		e.preventDefault();
		// クリックしたナビのテキストを保存
		var thisTxt = this.textContent;
		// マーカーの表示/非表示
		toggleShowMarkers(thisTxt);
		// 役場の詳細情報を非表示
		hideInfo(thisTxt);

		if (thisTxt === '全国(初期表示)') {
			// マーカーの吹き出しを非表示
			infoWindow.close();
			// 地図の表示位置を初期位置に戻す
			panZoomMap(initOption.latLng.lat, initOption.latLng.lng, initOption.zoom);
		} else {
			// 各々のdata属性の値を元に地図を拡大表示
			panZoomMap(this.dataset.lat, this.dataset.lng, this.dataset.zoom);
		}
	});
}

// 現在地取得ボタンクリック時
getPositionBtn.addEventListener('click', function () {
	// 吹き出しを非表示
	infoWindow.close();
	// 非表示になっているマーカーを全て表示
	toggleShowMarkers('全国(初期表示)');
	// Geolocation APIに対応している場合
	if(navigator.geolocation) {
		// 現在位置を取得
		getPosition();
	}
	else {
		alert( "お使いの端末では、現在位置を取得できません。");
	}
});


/*=====================================
* 関数群
=====================================*/

/**
* 初期表示用の関数
*/
function initMap() {
	// マップを作成(インスタンスを保存)
	map = new google.maps.Map(document.getElementById('js-map'), {
		center: initOption.latLng, // 表示位置の中心座標
		zoom: initOption.zoom // 表示倍率
	});
	// マーカーの初期設定
	initMaker();
}

/**
* マーカーの初期設定の関数
*/
function initMaker() {
	$.ajax({
		url: 'data/data.json',
		dataType: 'json'
	})
	/* json取得できた際の処理
	----------------------------------*/
	.done(function (data) {
		// 取得したjsonを変数に入れる
		jsonData = data;

		for (var i = 0; i < data.length; i++) {
			var element = data[i];

			// 区分別によってアイコン画像を分ける
			var image;
			if (element['区分'] === '都道府県庁') {
				image = 'img/icn_special.png';
			} else {
				image = 'img/icn_normal.png';
			}

			// マーカーの作成
			var marker = new google.maps.Marker({
				position: {
					lat: Number(element['緯度']), // 位置を設定
					lng: Number(element['経度'])
				},
				map: map, // 表示するマップを設定
				icon: image, // アイコン画像を設定
				// 必要な情報を取得
				detail: {
					name: element['名称'],
					areaName: element['都道府県'],
					zipCode: element['郵便番号'],
					address: element['住所'],
					tel: element['電話番号']
				}
			});

			// マーカーのオブジェクトを配列に格納
			markersArray.push(marker);

			// マーカーの情報を表示するウィンドウ
			infoWindow = new google.maps.InfoWindow();

			// マーカーをクリックした時の処理
			marker.addListener('click', function () {
				// 店舗情報を作成
				makeInfo(this.detail);
				// その他の店舗の情報を作成する
				makeOthers(this.detail.areaName, this.detail.name);
				// 作成した店舗情報を表示
				showInfo();
				// そのマーカーの地点を拡大表示
				panZoomMap(this.position.lat(), this.position.lng(), 16);
			});

			// マーカーをマウスオーバーした時の処理
			marker.addListener('mouseover', function () {
				infoWindow.setContent(this.detail.name);
				infoWindow.open(map, this);
			});
		}
	})
	/* json取得できなかった際の処理
	----------------------------------*/
	.fail(function () {
		alert('データの読込に失敗しました。')
	});
}

/**
* 店舗情報を表示する関数
*/
function makeInfo(detailObj) {
	var htm = '<dl>';
		htm += '	<dt>' + detailObj.name + '</dt>';
		htm += '	<dd>〒' + detailObj.zipCode + ' ' + detailObj.address + '</dd>';
		htm += '	<dd>電話番号:' + detailObj.tel + '</dd>';
		htm += '</dl>';
	infoBox.innerHTML = htm;
}

/**
* マーカーを表示/非表示する関数
*/
function toggleShowMarkers(areaName) {
	// 非表示になっているマーカーがあれば表示する
	if (currentClearMarkersArray.length !== 0) {
		for (var i = 0; i < currentClearMarkersArray.length; i++) {
			currentClearMarkersArray[i].setMap(map);
		}
	}
	// クリックしたテキストが全国だったら以降の処理を行わない
	if (areaName === '全国(初期表示)') {
		return;
	}
	// 同じ都道府県名ではない要素の配列を取得
	newClearMarkersArray = markersArray.filter(function (elm) {
		return elm.detail.areaName !== areaName;
	});
	// 取得した配列要素を非表示にする
	for (var i = 0; i < newClearMarkersArray.length; i++) {
		newClearMarkersArray[i].setMap(null);
	}
	// 新しく取得した配列を次に表示させるために代入
	currentClearMarkersArray = newClearMarkersArray;
}

/**
* 指定位置を中心に地図を拡大・移動する関数
*/
function panZoomMap(lat, lng, zoomNum) {
	map.panTo(new google.maps.LatLng(Number(lat), Number(lng)));
	map.setZoom(Number(zoomNum));
}

/**
* その他の店舗の情報を作成する関数
*/
function makeOthers(areaName, name) {
	// 同じ都道府県の要素の配列を取得
	var sameAreaArray = jsonData.filter(function (elm) {
		return (elm['都道府県'] === areaName) && (elm['名称'] !== name);
	});

	var htm = '';
	for (var i = 0; i < sameAreaArray.length; i++) {
		htm += '<tr>';
		htm += '	<td>' + sameAreaArray[i]['区分'] + '</td>';
		htm += '	<td>' + sameAreaArray[i]['名称'] + '</td>';
		htm += '	<td>' + sameAreaArray[i]['郵便番号'] + '</td>';
		htm += '	<td>' + sameAreaArray[i]['住所'] + '</td>';
		htm += '	<td>' + sameAreaArray[i]['電話番号'] + '</td>';
		htm += '</tr>';
	}
	otherTableTbody.innerHTML = htm;
}

/**
* 店舗情報を非表示する関数
*/
function hideInfo(areaName) {
	newAreaName = areaName;
	// 同じナビをクリックしたら以降の処理を行わない
	if (newAreaName === currentAreaName) {
		return;
	}
	$(messageTxt).removeClass(CLASS_HIDDEN);
	$(otherTable).addClass(CLASS_HIDDEN);
	$(infoBox).addClass(CLASS_HIDDEN);
	currentAreaName = newAreaName;
}

/**
* 店舗情報を表示する関数
*/
function showInfo() {
	$(messageTxt).addClass(CLASS_HIDDEN);
	$(otherTable).removeClass(CLASS_HIDDEN);
	$(infoBox).removeClass(CLASS_HIDDEN);
}

/*
* 現在位置を取得する関数
*/
function getPosition(){
	var geoOptions = {
		enableHighAccuracy: true,
		timeout: 60000,
		maximumAge: 0
	};
	navigator.geolocation.getCurrentPosition(successFunc, errorFunc, geoOptions);
}

/*
* 現在地の取得に成功した際の処理関数
*/
function successFunc(position) {
	// もし現在地を示すマーカーがすでにあったら削除する
	if (markerNowPosition !== null) {
		markerNowPosition.setMap(null);
	}
	var nowLat = position.coords.latitude; // 現在地の緯度
	var nowLng = position.coords.longitude; // 現在地の経度
	var nowlatlng = new google.maps.LatLng(nowLat, nowLng); // 現在地を元にした位置情報

	// 現在地を示すマーカーをセット
	markerNowPosition = new google.maps.Marker({
		position: nowlatlng,
		animation: google.maps.Animation.DROP,
		map: map
	});
	// 現在地を中心に表示する
	panZoomMap(nowLat, nowLng, 12);
}

/**
* 現在地の取得に失敗した際の処理関数
*/
function errorFunc(error) {
	var errorMsg = {
		0: "原因不明のエラーが発生し現在位置を取得できませんでした。",
		1: "位置情報の取得が許可されませんでした。",
		2: "電波状況などで位置情報が取得できませんでした。",
		3: "位置情報の取得に時間がかかり過ぎタイムアウトしました。"
	}
	alert(errorMsg[error.code]);
}

なるべく細かくコメントを入れたのですが、分かりやすい自信がありません。ざっくりとですが説明します。

 

<必要な変数の準備>

/*=====================================
* 変数の定義
=====================================*/
var map; // マップのインスタンスを格納する変数

var initOption = { // マップ初期表示用のオプション
	latLng: {
		lat: 35.689634,
		lng: 135.692101
	},
	zoom: 4
};
var getPositionBtn = document.getElementById('js-btnGetPosition'); // 現在地取得ボタン
var messageTxt = document.getElementById('js-message');            // クリック訴求のメッセージ
var infoBox = document.getElementById('js-info');                  // クリックした役場の詳細情報を表示するエリア
var otherTable = document.getElementById('js-other');              // 同じ都道府県にあるその他役場を表示するテーブル
var otherTableTbody = document.querySelector('#js-other tbody');   // 上記テーブルのtbody

var markerNowPosition = null;                         // 現在地を示すマーカー
var jsonData;                                         // 取得したjsonデータ
var markersArray = [];                                // マーカーの配列
var navList = document.querySelectorAll('#js-nav a'); // ナビ
var newClearMarkersArray = [];                        // これから非表示にするマーカーの配列
var currentClearMarkersArray = [];                    // 非表示になっているマーカーの配列
var infoWindow;                                       // 店舗名を表示する吹き出し

var CLASS_HIDDEN = 'is-hidden'; // 非表示用のクラス名

var newAreaName;     // クリックしたナビのテキスト
var currentAreaName; // 比較するための変数

関数をまたいで使われる変数を用意しておきます。それぞれの用途についてはコード上のコメントをご覧ください。

 

<関数>

/**
* 初期表示用の関数
*/
function initMap() {
	// マップを作成(インスタンスを保存)
	map = new google.maps.Map(document.getElementById('js-map'), {
		center: initOption.latLng, // 表示位置の中心座標
		zoom: initOption.zoom // 表示倍率
	});
	// マーカーの初期設定
	initMaker();
}

マップエリアにgoogle mapを表示させ、マーカーに関する関数の実行を行っています。
Mapクラスの第二引数で、マップ上で中心に位置する座標と、地図の倍率を設定しています。
ここで作成したマップのインスタンスは変数に保存しておき、以降の処理で使いまわしていきます。

 

/**
* マーカーの初期設定の関数
*/
function initMaker() {
	$.ajax({
		url: 'data/data.json',
		dataType: 'json'
	})
	/* json取得できた際の処理
	----------------------------------*/
	.done(function (data) {
		// 取得したjsonを変数に入れる
		jsonData = data;

		for (var i = 0; i < data.length; i++) {
			var element = data[i];

			// 区分別によってアイコン画像を分ける
			var image;
			if (element['区分'] === '都道府県庁') {
				image = 'img/icn_special.png';
			} else {
				image = 'img/icn_normal.png';
			}

			// マーカーの作成
			var marker = new google.maps.Marker({
				position: {
					lat: Number(element['緯度']), // 位置を設定
					lng: Number(element['経度'])
				},
				map: map, // 表示するマップを設定
				icon: image, // アイコン画像を設定
				// 必要な情報を取得
				detail: {
					name: element['名称'],
					areaName: element['都道府県'],
					zipCode: element['郵便番号'],
					address: element['住所'],
					tel: element['電話番号']
				}
			});

			// マーカーのオブジェクトを配列に格納
			markersArray.push(marker);
			
			// マーカーの情報を表示するウィンドウ
			infoWindow = new google.maps.InfoWindow();

			// マーカーをクリックした時の処理
			marker.addListener('click', function () {
				// 店舗情報を作成
				makeInfo(this.detail);
				// その他の店舗の情報を作成する
				makeOthers(this.detail.areaName, this.detail.name);
				// 作成した店舗情報を表示
				showInfo();
				// そのマーカーの地点を拡大表示
				panZoomMap(this.position.lat(), this.position.lng(), 16);
			});

			// マーカーをマウスオーバーした時の処理
			marker.addListener('mouseover', function () {
				infoWindow.setContent(this.detail.name);
				infoWindow.open(map, this);
			});
		}
	})
	/* json取得できなかった際の処理
	----------------------------------*/
	.fail(function () {
		alert('データの読込に失敗しました。')
	});
}

マーカーに関する設定を行っています。
まずjQueryを使ってAjaxでdata.jsonを読み込みます。読み込みに成功したら、取得したデータを後々使いまわすために変数jsonDataに保存します。
データの数の分だけ、マーカーのオブジェクトを作成していきます。
なお、マーカーにdetailというプロパティを与えて、固有のデータを保存しておきます(134~139行目)。
マーカーをクリック時、マウスオーバー時に行う処理を設定しています。

 

/**
* 店舗情報を表示する関数
*/
function makeInfo(detailObj) {
	var htm = '<dl>';
		htm += '	<dt>' + detailObj.name + '</dt>';
		htm += '	<dd>〒' + detailObj.zipCode + ' ' + detailObj.address + '</dd>';
		htm += '	<dd>電話番号:' + detailObj.tel + '</dd>';
		htm += '</dl>';
	infoBox.innerHTML = htm;
}

クリックしたマーカーの詳細情報からHTMLを作成・書き換えを行っています。

 

/**
* マーカーを表示/非表示する関数
*/
function toggleShowMarkers(areaName) {
	// 非表示になっているマーカーがあれば表示する
	if (currentClearMarkersArray.length !== 0) {
		for (var i = 0; i < currentClearMarkersArray.length; i++) {
			currentClearMarkersArray[i].setMap(map);
		}
	}
	// クリックしたテキストが全国だったら以降の処理を行わない
	if (areaName === '全国(初期表示)') {
		return;
	}
	// 同じ都道府県名ではない要素の配列を取得
	newClearMarkersArray = markersArray.filter(function (elm) {
		return elm.detail.areaName !== areaName;
	});
	// 取得した配列要素を非表示にする
	for (var i = 0; i < newClearMarkersArray.length; i++) {
		newClearMarkersArray[i].setMap(null);
	}
	// 新しく取得した配列を次に表示させるために代入
	currentClearMarkersArray = newClearMarkersArray;

ナビをクリックした時にマーカーの表示/非表示を切り替えます。
currentClearMarkersArrayは非表示になっているマーカーを表示するための配列です。
ナビでクリックした都道府県と同じ都道府県の以外のマーカーを配列に格納して非表示にしています。

 

/**
* 指定位置を中心に地図を拡大・移動する関数
*/
function panZoomMap(lat, lng, zoomNum) {
	map.panTo(new google.maps.LatLng(Number(lat), Number(lng)));
	map.setZoom(Number(zoomNum));
}

引数に与えた緯度、経度、ズーム値をもとに地図の表示を切り替えます。

 

/**
* その他の店舗の情報を作成する関数
*/
function makeOthers(areaName, name) {
	// 同じ都道府県の要素の配列を取得
	var sameAreaArray = jsonData.filter(function (elm) {
		return (elm['都道府県'] === areaName) && (elm['名称'] !== name);
	});

	var htm = '';
	for (var i = 0; i < sameAreaArray.length; i++) {
		htm += '<tr>';
		htm += '	<td>' + sameAreaArray[i]['区分'] + '</td>';
		htm += '	<td>' + sameAreaArray[i]['名称'] + '</td>';
		htm += '	<td>' + sameAreaArray[i]['郵便番号'] + '</td>';
		htm += '	<td>' + sameAreaArray[i]['住所'] + '</td>';
		htm += '	<td>' + sameAreaArray[i]['電話番号'] + '</td>';
		htm += '</tr>';
	}
	otherTableTbody.innerHTML = htm;
}

マーカーがクリックされた際に、同じ都道府県の役場のHTMLを作成・書き換えを行っています。
最初に取得したjsonデータから、クリックしたマーカーと同じ都道府県、かつ同名の役場でない要素の配列を取得します。
その配列からHTMLを作成しています。
 

/**
* 役場情報を非表示する関数
*/
function hideInfo(areaName) {
	newAreaName = areaName;
	// 同じナビをクリックしたら以降の処理を行わない
	if (newAreaName === currentAreaName) {
		return;
	}
	$(messageTxt).removeClass(CLASS_HIDDEN);
	$(otherTable).addClass(CLASS_HIDDEN);
	$(infoBox).addClass(CLASS_HIDDEN);
	currentAreaName = newAreaName;
}

ナビをクリックした際に役場情報(詳細情報と、同じ都道府県の役場情報)を非表示にします。
変数newAreaNameは今クリックされたナビの都道府県名、変数currentAreaNameは一つ前にクリックされたナビの都道府県名を格納しています。
これらを比較して以降の処理を行っています。
なお表示/非表示の切り替えにはクラス名の追加/削除(jQuery)で行っています。

 

/**
* 役場情報を表示する関数
*/
function showInfo() {
	$(messageTxt).addClass(CLASS_HIDDEN);
	$(otherTable).removeClass(CLASS_HIDDEN);
	$(infoBox).removeClass(CLASS_HIDDEN);
}

hideInfo関数とは逆に、表示する関数です。

 

/*
* 現在位置を取得する関数
*/
function getPosition(){
	var geoOptions = {
		enableHighAccuracy: true,
		timeout: 60000,
		maximumAge: 0
	};
	navigator.geolocation.getCurrentPosition(successFunc, errorFunc, geoOptions);
}

現在地の取得を試みて、成功したらsuccessFunc関数を、失敗したらerrorFunc関数を実行します。
geoOptionsは現在地の取得に関するオプションです。
簡単に説明すると、より精度の高い情報を取得するように試みて、取得に1分以上かかったらタイムアウト・エラーになります。

 

/*
* 現在地の取得に成功した際の処理関数
*/
function successFunc(position) {
	// もし現在地を示すマーカーがすでにあったら削除する
	if (markerNowPosition !== null) {
		markerNowPosition.setMap(null);
	}
	var nowLat = position.coords.latitude; // 現在地の緯度
	var nowLng = position.coords.longitude; // 現在地の経度
	var nowlatlng = new google.maps.LatLng(nowLat, nowLng); // 現在地を元にした位置情報

	// 現在地を示すマーカーをセット
	markerNowPosition = new google.maps.Marker({
		position: nowlatlng,
		animation: google.maps.Animation.DROP,
		map: map
	});
	// 現在地を中心に表示する
	panZoomMap(nowLat, nowLng, 12);
}

現在地の取得に成功したら現在地を示すマーカーをセットし、その位置を地図の中心に表示します。

 

/**
* 現在地の取得に失敗した際の処理関数
*/
function errorFunc(error) {
	var errorMsg = {
		0: "原因不明のエラーが発生し現在位置を取得できませんでした。",
		1: "位置情報の取得が許可されませんでした。",
		2: "電波状況などで位置情報が取得できませんでした。",
		3: "位置情報の取得に時間がかかり過ぎタイムアウトしました。"
	}
	alert(errorMsg[error.code]);
}

現在地の取得に失敗したら、その理由に応じたメッセージをアラート表示します。

なお現在地の取得に関しては下記記事が大変参考になりますので参照頂けると理解が深まると思います(一部コードを拝借させて頂いております)。
JavaScriptで位置情報を取得する方法(Geolocation API)|Syncer

 

Google Maps API利用上の注意

1日25,000リクエストまでは無料で使えます。それを超えると費用が発生します。

参考URL:

■無償版と有償版があります。無償版でも利用規約に反しない範囲であれば商用利用は可能です。
下記の記事がわかりやすくまとまっているのでご覧ください。
GoogleMapAPIの商用利用 – Qiita

 

最後に

大まかではありますが、実装の方法をご紹介しました。
Google Maps APIは私自身にとって初めてしっかりと触ったAPIなのですが、ドキュメントもしっかりしていて、google検索等すると情報も豊富に出てくるので理解しやすかったです。
今回作成したデモはシンプルな機能しかありませんが、冒頭で紹介したサイトのようなマップも、今回の応用で作成できると思います。
 

参考ページ