概要
このチュートリアルでは、Firebase アプリケーション プラットフォームを使用してインタラクティブなマップを作成する方法を説明します。以下のマップのさまざまな場所をクリックして、ヒートマップを作成してみましょう。
以下のセクションに、このチュートリアルでマップを作成するために必要なコード全体を載せています。
/** * Reference to Firebase database. * @const */ var firebase = new Firebase('https://fire-map-tutorial.firebaseio.com/'); /** * Data object to be written to Firebase. */ var data = { sender: null, timestamp: null, lat: null, lng: null }; function makeInfoBox(controlDiv, map) { // Set CSS for the control border. var controlUI = document.createElement('div'); controlUI.style.boxShadow = 'rgba(0, 0, 0, 0.298039) 0px 1px 4px -1px'; controlUI.style.backgroundColor = '#fff'; controlUI.style.border = '2px solid #fff'; controlUI.style.borderRadius = '2px'; controlUI.style.marginBottom = '22px'; controlUI.style.marginTop = '10px'; controlUI.style.textAlign = 'center'; controlDiv.appendChild(controlUI); // Set CSS for the control interior. var controlText = document.createElement('div'); controlText.style.color = 'rgb(25,25,25)'; controlText.style.fontFamily = 'Roboto,Arial,sans-serif'; controlText.style.fontSize = '100%'; controlText.style.padding = '6px'; controlText.textContent = 'The map shows all clicks made in the last 10 minutes.'; controlUI.appendChild(controlText); } /** * Starting point for running the program. Authenticates the user. * @param {function()} onAuthSuccess - Called when authentication succeeds. */ function initAuthentication(onAuthSuccess) { firebase.authAnonymously(function(error, authData) { if (error) { console.log('Login Failed!', error); } else { data.sender = authData.uid; onAuthSuccess(); } }, {remember: 'sessionOnly'}); // Users will get a new id for every session. } /** * Creates a map object with a click listener and a heatmap. */ function initMap() { var map = new google.maps.Map(document.getElementById('map'), { center: {lat: 0, lng: 0}, zoom: 3, styles: [{ featureType: 'poi', stylers: [{ visibility: 'off' }] // Turn off POI. }, { featureType: 'transit.station', stylers: [{ visibility: 'off' }] // Turn off bus, train stations etc. }], disableDoubleClickZoom: true, streetViewControl: false, }); // Create the DIV to hold the control and call the makeInfoBox() constructor // passing in this DIV. var infoBoxDiv = document.createElement('div'); makeInfoBox(infoBoxDiv, map); map.controls[google.maps.ControlPosition.TOP_CENTER].push(infoBoxDiv); // Listen for clicks and add the location of the click to firebase. map.addListener('click', function(e) { data.lat = e.latLng.lat(); data.lng = e.latLng.lng(); addToFirebase(data); }); // Create a heatmap. var heatmap = new google.maps.visualization.HeatmapLayer({ data: [], map: map, radius: 16 }); initAuthentication(initFirebase.bind(undefined, heatmap)); } /** * Set up a Firebase with deletion on clicks older than expirySeconds * @param {!google.maps.visualization.HeatmapLayer} heatmap The heatmap to * which points are added from Firebase. */ function initFirebase(heatmap) { // 10 minutes before current time. var startTime = new Date().getTime() - (60 * 10 * 1000); // Reference to the clicks in Firebase. var clicks = firebase.child('clicks'); // Listener for when a click is added. clicks.orderByChild('timestamp').startAt(startTime).on('child_added', function(snapshot) { // Get that click from firebase. var newPosition = snapshot.val(); var point = new google.maps.LatLng(newPosition.lat, newPosition.lng); var elapsed = new Date().getTime() - newPosition.timestamp; // Add the point to the heatmap. heatmap.getData().push(point); // Requests entries older than expiry time (10 minutes). var expirySeconds = Math.max(60 * 10 * 1000 - elapsed, 0); // Set client timeout to remove the point after a certain time. window.setTimeout(function() { // Delete the old point from the database. snapshot.ref().remove(); }, expirySeconds); } ); // Remove old data from the heatmap when a point is removed from firebase. clicks.on('child_removed', function(snapshot, prevChildKey) { var heatmapData = heatmap.getData(); var i = 0; while (snapshot.val().lat != heatmapData.getAt(i).lat() || snapshot.val().lng != heatmapData.getAt(i).lng()) { i++; } heatmapData.removeAt(i); }); } /** * Updates the last_message/ path with the current timestamp. * @param {function(Date)} addClick After the last message timestamp has been updated, * this function is called with the current timestamp to add the * click to the firebase. */ function getTimestamp(addClick) { // Reference to location for saving the last click time. var ref = firebase.child('last_message/' + data.sender); ref.onDisconnect().remove(); // Delete reference from firebase on disconnect. // Set value to timestamp. ref.set(Firebase.ServerValue.TIMESTAMP, function(err) { if (err) { // Write to last message was unsuccessful. console.log(err); } else { // Write to last message was successful. ref.once('value', function(snap) { addClick(snap.val()); // Add click with same timestamp. }, function(err) { console.warn(err); }); } }); } /** * Adds a click to firebase. * @param {Object} data The data to be added to firebase. * It contains the lat, lng, sender and timestamp. */ function addToFirebase(data) { getTimestamp(function(timestamp) { // Add the new timestamp to the record data. data.timestamp = timestamp; var ref = firebase.child('clicks').push(data, function(err) { if (err) { // Data was not written to firebase. console.warn(err); } }); }); }
<div id="map"></div>
/* Always set the map height explicitly to define the size of the div * element that contains the map. */ #map { height: 100%; } /* Optional: Makes the sample page fill the window. */ html, body { height: 100%; margin: 0; padding: 0; }
<!-- Replace the value of the key parameter with your own API key. --> <script src="https://maps.googleapis.com/maps/api/js?key=AIzaSyCkUOdZ5y7hMm0yrcCQoCvLwzdM6M8s5qk&libraries=visualization&callback=initMap" async defer></script> <script src="https://cdn.firebase.com/js/client/2.3.2/firebase.js"></script>
実際に試す
コードブロックの右上にカーソルを合わせて、コードをコピーするか、コードを JSFiddl で開きます。
<!DOCTYPE html> <html> <head> <style> /* Always set the map height explicitly to define the size of the div * element that contains the map. */ #map { height: 100%; } /* Optional: Makes the sample page fill the window. */ html, body { height: 100%; margin: 0; padding: 0; } </style> </head> <body> <div id="map"></div> <script src="https://cdn.firebase.com/js/client/2.3.2/firebase.js"></script> <script> /** * Reference to Firebase database. * @const */ var firebase = new Firebase('https://fire-map-tutorial.firebaseio.com/'); /** * Data object to be written to Firebase. */ var data = { sender: null, timestamp: null, lat: null, lng: null }; function makeInfoBox(controlDiv, map) { // Set CSS for the control border. var controlUI = document.createElement('div'); controlUI.style.boxShadow = 'rgba(0, 0, 0, 0.298039) 0px 1px 4px -1px'; controlUI.style.backgroundColor = '#fff'; controlUI.style.border = '2px solid #fff'; controlUI.style.borderRadius = '2px'; controlUI.style.marginBottom = '22px'; controlUI.style.marginTop = '10px'; controlUI.style.textAlign = 'center'; controlDiv.appendChild(controlUI); // Set CSS for the control interior. var controlText = document.createElement('div'); controlText.style.color = 'rgb(25,25,25)'; controlText.style.fontFamily = 'Roboto,Arial,sans-serif'; controlText.style.fontSize = '100%'; controlText.style.padding = '6px'; controlText.textContent = 'The map shows all clicks made in the last 10 minutes.'; controlUI.appendChild(controlText); } /** * Starting point for running the program. Authenticates the user. * @param {function()} onAuthSuccess - Called when authentication succeeds. */ function initAuthentication(onAuthSuccess) { firebase.authAnonymously(function(error, authData) { if (error) { console.log('Login Failed!', error); } else { data.sender = authData.uid; onAuthSuccess(); } }, {remember: 'sessionOnly'}); // Users will get a new id for every session. } /** * Creates a map object with a click listener and a heatmap. */ function initMap() { var map = new google.maps.Map(document.getElementById('map'), { center: {lat: 0, lng: 0}, zoom: 3, styles: [{ featureType: 'poi', stylers: [{ visibility: 'off' }] // Turn off POI. }, { featureType: 'transit.station', stylers: [{ visibility: 'off' }] // Turn off bus, train stations etc. }], disableDoubleClickZoom: true, streetViewControl: false, }); // Create the DIV to hold the control and call the makeInfoBox() constructor // passing in this DIV. var infoBoxDiv = document.createElement('div'); makeInfoBox(infoBoxDiv, map); map.controls[google.maps.ControlPosition.TOP_CENTER].push(infoBoxDiv); // Listen for clicks and add the location of the click to firebase. map.addListener('click', function(e) { data.lat = e.latLng.lat(); data.lng = e.latLng.lng(); addToFirebase(data); }); // Create a heatmap. var heatmap = new google.maps.visualization.HeatmapLayer({ data: [], map: map, radius: 16 }); initAuthentication(initFirebase.bind(undefined, heatmap)); } /** * Set up a Firebase with deletion on clicks older than expirySeconds * @param {!google.maps.visualization.HeatmapLayer} heatmap The heatmap to * which points are added from Firebase. */ function initFirebase(heatmap) { // 10 minutes before current time. var startTime = new Date().getTime() - (60 * 10 * 1000); // Reference to the clicks in Firebase. var clicks = firebase.child('clicks'); // Listener for when a click is added. clicks.orderByChild('timestamp').startAt(startTime).on('child_added', function(snapshot) { // Get that click from firebase. var newPosition = snapshot.val(); var point = new google.maps.LatLng(newPosition.lat, newPosition.lng); var elapsed = new Date().getTime() - newPosition.timestamp; // Add the point to the heatmap. heatmap.getData().push(point); // Requests entries older than expiry time (10 minutes). var expirySeconds = Math.max(60 * 10 * 1000 - elapsed, 0); // Set client timeout to remove the point after a certain time. window.setTimeout(function() { // Delete the old point from the database. snapshot.ref().remove(); }, expirySeconds); } ); // Remove old data from the heatmap when a point is removed from firebase. clicks.on('child_removed', function(snapshot, prevChildKey) { var heatmapData = heatmap.getData(); var i = 0; while (snapshot.val().lat != heatmapData.getAt(i).lat() || snapshot.val().lng != heatmapData.getAt(i).lng()) { i++; } heatmapData.removeAt(i); }); } /** * Updates the last_message/ path with the current timestamp. * @param {function(Date)} addClick After the last message timestamp has been updated, * this function is called with the current timestamp to add the * click to the firebase. */ function getTimestamp(addClick) { // Reference to location for saving the last click time. var ref = firebase.child('last_message/' + data.sender); ref.onDisconnect().remove(); // Delete reference from firebase on disconnect. // Set value to timestamp. ref.set(Firebase.ServerValue.TIMESTAMP, function(err) { if (err) { // Write to last message was unsuccessful. console.log(err); } else { // Write to last message was successful. ref.once('value', function(snap) { addClick(snap.val()); // Add click with same timestamp. }, function(err) { console.warn(err); }); } }); } /** * Adds a click to firebase. * @param {Object} data The data to be added to firebase. * It contains the lat, lng, sender and timestamp. */ function addToFirebase(data) { getTimestamp(function(timestamp) { // Add the new timestamp to the record data. data.timestamp = timestamp; var ref = firebase.child('clicks').push(data, function(err) { if (err) { // Data was not written to firebase. console.warn(err); } }); }); } </script> <script async defer src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&libraries=visualization&callback=initMap"> </script> </body> </html>
はじめに
このチュートリアルのコードを使用して、独自の Firebase マップを作成できます。そのためには、まずテキスト エディタで新しいファイルを作成し、index.html
という名前で保存します。
以降のセクションを読み、このファイルに追加できるコードについて確認してください。
基本的なマップの作成
このセクションでは、基本的なマップを構成するコードについて説明します。この内容は、Google Maps JavaScript API のスタートガイドで紹介しているマップの作成方法と似ています。
以下のコードを index.html
ファイルにコピーします。このコードは Google Maps JavaScript API を読み込み、マップを全画面に表示します。さらに可視化ライブラリを読み込みます。このライブラリは、チュートリアルの後半でヒートマップを作成するときに必要になります。
<!DOCTYPE html> <html> <head> <style type="text/css"> #map { height: 100%; } html, body { height: 100%; margin: 0; padding: 0; } </style> </head> <body> <div id="map"></div> <script async defer src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY &libraries=visualization&callback=initMap"> </script> /** * The JavaScript code that creates the firebase map goes between the empty script tags below. */ <script>firebasemap.js</script> </body> </html>
コードサンプルの YOUR_API_KEY
をクリックするか、こちらの手順に従って API キーを取得します。YOUR_API_KEY
を、自身のアプリケーションの API キーに置き換えます。
以下のセクションでは、Firebase マップを作成する JavaScript コードについて説明します。このコードを firebasemap.js
ファイルにコピーして保存し、それを以下のように script タグ間で参照します。
<script>firebasemap.js</script>または、このチュートリアルの冒頭で紹介したサンプルコード全体のように、script タグ間にコードを直接挿入することもできます。
以下のコードを firebasemap.js
ファイルに追加するか、index.html
ファイルの空の script タグ間に追加します。このコードがプログラムを実行する出発点になります。まず、マップ オブジェクトを初期化する関数を作成します。
function initMap() { var map = new google.maps.Map(document.getElementById('map'), { center: {lat: 0, lng: 0}, zoom: 3, styles: [{ featureType: 'poi', stylers: [{ visibility: 'off' }] // Turn off points of interest. }, { featureType: 'transit.station', stylers: [{ visibility: 'off' }] // Turn off bus stations, train stations, etc. }], disableDoubleClickZoom: true }); }
クリック可能なヒートマップを使いやすくするため、上記のコードで(クリックすると情報ウィンドウが表示される)有名スポットや交通機関の駅を無効にする必要があります。また、誤ってズームしないようにダブルクリックによるズームも無効にします。マップ機能の詳細については、ドキュメントをご覧ください。
API を完全に読み込むと、HTML ファイルの以下の script タグに指定された callback パラメータによって initMap()
関数が実行されます。
<script async defer src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY &libraries=visualization&callback=initMap">
以下のコードを追加して、マップの上部にテキスト コントロールを作成します。
function makeInfoBox(controlDiv, map) { // Set CSS for the control border. var controlUI = document.createElement('div'); controlUI.style.boxShadow = 'rgba(0, 0, 0, 0.298039) 0px 1px 4px -1px'; controlUI.style.backgroundColor = '#fff'; controlUI.style.border = '2px solid #fff'; controlUI.style.borderRadius = '2px'; controlUI.style.marginBottom = '22px'; controlUI.style.marginTop = '10px'; controlUI.style.textAlign = 'center'; controlDiv.appendChild(controlUI); // Set CSS for the control interior. var controlText = document.createElement('div'); controlText.style.color = 'rgb(25,25,25)'; controlText.style.fontFamily = 'Roboto,Arial,sans-serif'; controlText.style.fontSize = '100%'; controlText.style.padding = '6px'; controlText.innerText = 'The map shows all clicks made in the last 10 minutes.'; controlUI.appendChild(controlText); }
テキスト コントロール ボックスを読み込むには、以下のコードを initMap
関数内の var map
の後に追加します。
// Create the DIV to hold the control and call the makeInfoBox() constructor // passing in this DIV. var infoBoxDiv = document.createElement('div'); var infoBox = new makeInfoBox(infoBoxDiv, map); infoBoxDiv.index = 1; map.controls[google.maps.ControlPosition.TOP_CENTER].push(infoBoxDiv);お試しください
このコードで作成される Google マップを表示するには、ウェブブラウザで index.html
を開きます。
Firebase の設定
このアプリケーションをコラボレーティブにするには、すべてのユーザーがアクセスできる外部データベースにクリックデータを保存する必要があります。それには、Firebase Realtime Database が便利です。SQL の知識も必要ありません。
まず、無料の Firebase アカウントにサインアップします。初めて Firebase を使用する場合は、「My First App」という名前の新しいアプリが表示されます。アプリを新規作成する場合は、アプリに新しい名前を付けて、カスタム Firebase URL(末尾が firebaseIO.com
)を設定します。たとえば、アプリに「Jane's Firebase Map」という名前を付けて、URL を https://janes-firebase-map.firebaseIO.com
のように指定できます。この URL を使用して、データベースを JavaScript アプリケーションにリンクさせることができます。
HTML ファイルの <head>
タグの後に以下の行を追加して、Firebase ライブラリをインポートします。
<script src="https://cdn.firebase.com/js/client/2.3.2/firebase.js"></script>
次の行を JavaScript ファイルに追加します。
var firebase = new Firebase("<Your Firebase URL here>");
Firebase へのクリックデータの保存
このセクションでは、マップ上でのクリックデータを Firebase に保存するコードについて説明します。
マップ上でクリックするたびに、以下のコードがグローバル データ オブジェクトを作成し、その情報を Firebase に保存します。このオブジェクトには latLng、クリックのタイムスタンプといったデータのほか、クリックが発生したブラウザの一意の ID が記録されます。
/** * Data object to be written to Firebase. */ var data = { sender: null, timestamp: null, lat: null, lng: null };
以下のコードは、クリックごとに一意のセッション ID を記録します。これにより、Firebase のセキュリティ規則に従ってマップ上のトラフィックのレートを制御できます。
/** * Starting point for running the program. Authenticates the user. * @param {function} Called when authentication succeeds. */ function initAuthentication(onAuthSuccess) { firebase.authAnonymously(function(error, authData) { if (error) { console.log('Login Failed!', error); } else { data.sender = authData.uid; onAuthSuccess(); } }, {remember: 'sessionOnly'}); // Users will get a new id for every session. }
以下に示すコードの次のセクションでは、マップ上でのクリックをリッスンし、Firebase データベースに「子」を追加します。この処理が発生すると、snapshot.val()
関数がエントリデータの値を取得し、新しい LatLng オブジェクトを作成します。
// Listener for when a click is added - add it to the heatmap. clicks.orderByChild('timestamp').startAt(startTime).on('child_added', function(snapshot) { var newPosition = snapshot.val(); var point = new google.maps.LatLng(newPosition.lat, newPosition.lng); heatmap.getData().push(point); } );
以下のコードは、リアルタイムで常にタイムスタンプを記録し、マップ上で 10 秒以上経過したクリックを削除するように Firebase を設定します。
/** * Set up a Firebase with deletion on clicks older than expirySeconds * @param {!google.maps.visualization.HeatmapLayer} heatmap The heatmap to * which points are added from Firebase. */ function initFirebase(heatmap) { // 10 minutes before current time. var startTime = new Date().getTime() - (60 * 10 * 1000); // Reference to the clicks in Firebase. var clicks = firebase.child('clicks'); // Remove old clicks. clicks.orderByChild('timestamp').endAt(startTime).on('child_added', function(snapshot) { snapshot.ref().remove(); } ); } /** * Adds a click to firebase. * @param {Object} data The data to be added to firebase. * It contains the lat, lng, sender and timestamp. */ function addToFirebase(data) { getTimestamp(function(timestamp) { // Add the new timestamp to the record data. data.timestamp = timestamp; var ref = firebase.child('clicks').push(data, function(err) { if (err) { // Data was not written to firebase. console.log(err); } }); }); } /** * Also called each time the map is clicked. * Updates the last_message/ path with the current timestamp. * @param {function(Date)} addClick After the last message timestamp has been updated, * this function is called with the current timestamp to add the * click to the firebase. */ function getTimestamp(addClick) { // Reference to location for saving the last click time. var ref = firebase.child('last_message/' + data.sender); ref.onDisconnect().remove(); // Delete reference from firebase on disconnect. // Set value to timestamp. ref.set(Firebase.ServerValue.TIMESTAMP, function(err) { if (err) { // Write to last message was unsuccessful. console.log(err); } else { // Write to last message was successful. ref.once('value', function(snap) { addClick(snap.val()); // Add click with same timestamp. }, function(err) { console.log(err); }); } }); }
このセクションのすべての JavaScript コードを firebasemap.js
ファイルにコピーしてください。
ヒートマップの作成
次のステップでは、マップ上の各地点の相対的なクリック数をグラフィックで示します。詳細については、ヒートマップのガイドをご覧ください。
以下のコードを initMap()
関数内に追加し、ヒートマップを作成します。
// Create a heatmap. var heatmap = new google.maps.visualization.HeatmapLayer({ data: [], map: map, radius: 16 });
以下のコードは、initFirebase
、addToFirebase
、getTimestamp
の各関数を呼び出します。
initAuthentication(initFirebase.bind(undefined, heatmap));
次に、マーカー コードを以下の行に置き換えて、Firebase データベース内のエントリをこのヒートマップ上のポイントとして表示します。
heatmap.getData().push(point);
お試しください
ウェブブラウザで index.html
ファイルを再読み込みします。他のブラウザからこのマップをクリックすると、そのポイントがマップ上に表示されるはずです。
ヒートマップ上のポイントの作成
ヒートマップをクリックしても、まだポイントが作成されない点に注目してください。
以下のコードは initMap()
内でリスナーを追加します。これはマップを作成するコードの後に配置します。このコードは各クリックのデータをリッスンし、クリックの場所を Firebase データベースに保存し、ヒートマップ上にポイントを表示します。
// Listen for clicks and add the location of the click to firebase. map.addListener('click', function(e) { data.lat = e.latLng.lat(); data.lng = e.latLng.lng(); addToFirebase(data); });
お試しください
マップ上の場所をクリックすると、ヒートマップ上にポイントが作成されます。
これで、Firebase と Google Maps JavaScript API を使用した、完全に動作するリアルタイム アプリケーションを作成できました。
ヒートマップをクリックすると、クリックした場所の緯度と経度が Firebase データベースに記録されるはずです。このデータは、Firebase アカウントにログインして、アプリの [data] タブに移動すると確認できます。この時点で、誰かがマップをクリックすると、自分もそのユーザーもマップ上のポイントを見ることができます。クリックの場所は、ユーザーがページを閉じた後でも保持されます。リアルタイムのコラボレーション機能をテストするには、このページを異なる 2 つのウィンドウで開きます。リアルタイムで両方のウィンドウにマーカーが表示されるはずです。
さらに詳しく知る
Firebase はデータを JSON 形式で保存し、接続しているすべてのクライアントとリアルタイムで同期するアプリケーション プラットフォームです。アプリがオフラインになっても Firebase を使用できます。このチュートリアルでは、Firebase のリアルタイム データベースを使用しています。