GIS では、ジオフェンスと呼ばれる仮想のエリア(ポリゴン)をマップ上に作成することで、エリアへの入出をイベントとしてキャッチすることができます。この仕組みを利用して、顧客へのマーケティングや観光客へのプロモーションなどへの利用が期待されています。
今回は、店舗の位置情報を持つサンプルデータをもとに、店舗から半径 200m のジオフェンスに入った顧客へ、店舗の情報を含んだプッシュ通知を送るアプリを作成しました。筆者は JavaScript 製品担当なので、通知を受信するクライアント側は Web アプリ、通知を送信するサーバー側は Node.js で実装しています。作成したアプリは、ESRIジャパンの GitHub にて公開しています。
プッシュ通知を行うには、まず、ユーザーに通知を購読してもらいます。
購読の大まかな流れは以下です。
このあと説明しますが、Web ブラウザーのプッシュ通知には Service Worker を使用します。Service Worker は JS ファイルを登録することで、バックグラウンドで実行されます。
Service Worker を登録したら、購読先のサーバーへアプリケーション サーバー キーをリクエストします。キーを使用して、プッシュ サービス(プッシュ通知を送信するサービス)が、購読しているユーザーの判定と、購読先のサーバーが同じであることを確認します。認証には、VAPID という仕組みが使用されます。
キーの取得後、ユーザーへ、プッシュ通知の許可を確認するポップアップを表示します。通知が許可されたら、購読情報(PushSubscription オブジェクト)を生成します。
生成した購読情報はサーバーへ送られ、ユーザーへプッシュ通知を送る際に使用されます。
購読が完了したら、サーバーはユーザーへプッシュ通知を送ることが可能になります。
サーバーはプッシュ通知を、プッシュ サービスへリクエストします。リクエスト先のプッシュ サービスは、PushSubscription の endpoint に含まれています。使用されるプッシュ サービスは、ブラウザーにより異なり、設定することはできません。また、プッシュ通知は暗号化されている必要があります。
プッシュ サービスへプッシュ通知をリクエストすると、サービスはプッシュ通知をブラウザーへ送信します。
そして、プッシュ通知を受け取ったブラウザーはプッシュ イベントを実行し、プッシュ通知を表示します。
では、実際にプッシュ通知を表示してみましょう。
プッシュ通知を受信し、表示する Web アプリを作成します。
ブラウザーでプッシュ通知を受け取るには Service Worker という仕組みを使用します。Service Worker は、バックグラウンドで動作する JavaScript の実行環境です。Web ページを開いていなくても処理を実行することができるため、Web ページを閉じていても通知を受け取り表示することができます。
Service Worker を使用するには、JS ファイルを登録します。登録するとブラウザーは JS ファイルを Service Worker 上で動作させます。登録には、以下のコードのように、Service Worker API の register() に JS ファイルのパスを渡します。
js/main.js
function initServiceWorker() {
// ......
// Service Worker を登録
navigator.serviceWorker.register("sw.js").then(function(swReg) {
// ......
});
}
登録した Service Worker にプッシュ通知を表示する機能を実装します。
Service Worker には、ブラウザーがプッシュ通知をプッシュ サービスから受け取ったときに呼び出す Push イベント ハンドラーが用意されています。このイベントのコールバック関数に、通知を表示する showNotification() を設定します。第1引数に、表示するタイトル、第2引数に表示するコンテンツやアイコンなどのオプションを渡します。
また、Push のイベント オブジェクトには、プッシュ通知を送る際にサーバーが送信した情報が含まれています。ここでは、この情報をもとにタイトルとコンテンツの値を設定しています。
sw.js
// プッシュ通知のイベントハンドラー
self.addEventListener('push', function(e) {
// サーバーが送信した情報を取得
var json = e.data.json();// タイトルとオプションを設定
var title = json.title;
var options = {
body: json.body,
icon: 'images/icon.png',
badge: 'images/badge.png'
};// プッシュ通知を表示
e.waitUntil(
self.registration.showNotification(title, options)
);
});
Service Worker の設定は以上です。続いて、Web アプリでプッシュ通知を購読します。
プッシュ通知をサーバーから受け取るには、サーバーから認証情報を取得し、購読情報を生成して送信する必要があります。
通知の購読には、ServiceWorkerRegistration.pushManager.subscribe() を使用します。
ServiceWorkerRegistration.pushManager.subscribe() には、サーバーから取得した公開鍵を渡します。サーバー側の実装はこのあと説明しますが、”api/key” へのリクエストで公開鍵を取得することができます。
購読が成功すると、PushSubscription オブジェクトが返ります。PushSubscription には、プッシュ サービスのエンドポイントや認証情報など、通知に必要な情報が含まれており、サーバーは PushSubscription を使用してプッシュ サービスへ通知をリクエストします。
js/main.js
// 通知を購読する
function subscribeUser() {
// サーバーから公開鍵を取得
fetch("/api/key").then(function(res) {
if (res.ok) {
return res.text();
}
}).then(function(applicationServerPublicKey) {
// 公開鍵をエンコーディング
var applicationServerKey = urlB64ToUint8Array(applicationServerPublicKey);// 公開鍵を渡して、プッシュ通知を購読する
swRegistration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: applicationServerKey
}).then(function(subscription) {
// ......
});
});
}
プッシュ通知をリクエストする処理です。
実際にプッシュ サービスへ通知の送信をリクエストするのはサーバーですが、クライアント側で、サーバーへプッシュ サービスへリクエストを送るリクエストを投げます。
param には、サーバーがプッシュ サービスへ通知をリクエストするときに使用する PushSubscription と通知に表示するタイトルとコンテンツを渡します。
js/main.js
// 通知をリクエスト
function updateSubscriptionOnServer(param) {
fetch("/api/notification", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(param)
}).then(function(response) {
// ......
});
}
やっと GIS の出番です。
あらかじめ作成しておいた店舗から 200m のバッファー(ジオフェンス)に対してクエリを行います。現在地の情報をもとにバッファー内に入ったときに、関連する店舗の情報を通知します。
では、コードを見てみましょう。
現在地の取得は Geolocation API の watchPosition() を使用します。現在地が更新されたらクエリの処理を記述した initQuery() を実行します。
js/main.js
// 現在地の監視
function initWatchPosition() {
// 現在地を監視し、現在地が更新されたらクエリを実行
watchId = navigator.geolocation.watchPosition(function(position) {
initQuery(position);
});
}
クエリの処理を行います。
バッファー(ジオフェンス)にポイント(現在地)が含まれているかどうかをクエリするため、パラメーターに以下の3つを設定しています。
クエリを実行した結果、ポイントと交差するバッファーが返った場合に、手順4 で説明したプッシュ通知をリクエストする updateSubscriptionOnServer() を呼び出します。
js/main.js
function initQuery(position) {
// 現在地の緯度経度からポイントを作成
var point = new Point({
longitude: position.coords.longitude,
latitude: position.coords.latitude
});// クエリ パラメーターを作成
var query = new Query();
query.geometry = point;
query.outFields = ["OBJECTID", "SHOP_NAME", "MESSAGE"];
query.spatialRelationship = "intersects";// あらかじめ作成しておいた店舗から 200m のバッファー(ジオフェンス)に対してクエリを実行
targetLyr.queryFeatures(query).then(function(result){
// バッファー内にポイント(現在地)が含まれる場合
if (result.features.length > 0) {
var attr = result.features[0].attributes;
var objId = attr.OBJECTID;// バッファー内のポイントが同じではないとき、プッシュ通知をリクエスト
if (objId !== currentGeofence) {
currentGeofence = objId;swRegistration.pushManager.getSubscription().then(function(subscription) {
var param = {
subscription: subscription,
message: {
title: "いろは堂 " + attr.SHOP_NAME,
body: attr.MESSAGE
}
}
updateSubscriptionOnServer(param);
});
}
}
// ......
});
}
Web アプリ側の実装は以上です。次は、プッシュ サーバーへプッシュ通知をリクエストするサーバー側の実装を行います。
Web プッシュは、送信する際にメッセージを暗号化する必要があります。今回は、暗号化を行ってくれる web-push-libs を使用しました。
web-push モジュールを読み込み、generateVAPIDKeys() を使って、認証に使用する VAPID の公開鍵と秘密鍵を生成します。
生成した鍵をモジュールに設定します。設定時には、公開鍵と秘密鍵の他に、送信者の情報としてプッシュ サービスのリクエストに含めるためメールアドレスが必要です。
server.js
// web-push-libs の読み込み
const webpush = require('web-push');// VAPID の生成
const vapidKeys = webpush.generateVAPIDKeys();// VAPID の設定
webpush.setVapidDetails(
'mailto:example@yourdomain.org',
vapidKeys.publicKey,
vapidKeys.privateKey
);
公開鍵をクライアントへ送信します。
今回は、クライアントから送られてきたリクエストをリクエスト先の URL に応じて処理を分けるために Express を使用しました。ここでは、クライアントから ‘api/key’ へリクエストを送られたときに、公開鍵を返します。
「プッシュ通知を受信する」の手順3で、この URL へリクエストが送られています。
server.js
// クライアントへ公開鍵を返す
app.get('/api/key', (req, res) => {
res.send(vapidKeys.publicKey);
});
いよいよプッシュ通知をプッシュ サービスへリクエストします。
PushSubscription、コンテンツを定義したペイロード、そして鍵を含むオプションを sendNotification() に渡すと、プッシュ サービスへ通知がリクエストされます。
リクエストが成功し、プッシュ サービスからプッシュ通知が送信されると、通知を受け取ったブラウザーは Service Worker の push イベントを実行し、通知が表示されます。
server.js
// プッシュ通知をプッシュ サーバーへ送信
app.post('/api/notification', (req, res) => {
const pushSubscription = req.body.subscription;
const payload = JSON.stringify(req.body.message);
const options = {
vapidDetails: {
subject: 'http://localhost:3000/',
publicKey: vapidKeys.publicKey,
privateKey: vapidKeys.privateKey
}
}webpush.sendNotification(
pushSubscription,
payload,
options
);
});
それでは、サーバーを立ち上げ、Web ページを開いてみましょう。
GitHub のサンプルをローカル環境へクローンまたはダウンロードします。
コマンドプロンプトを起動し、“npm install” を入力し、サンプルで使用しているモジュールをインストールししたら、“npm start” を入力し、Web ブラウザーから http://localhost:3000 へアクセスしてみてください。
※ IE11、Edge および Safari は Service Worker をサポートしていません。Chrome または Firefox をお試しください。
[プッシュ通知を有効化] をクリックすると、「プッシュ通知を受信する」の手順3で説明した、通知を購読する subscribeUser() が実行され、通知の表示の許可を求めるポップアップが表示されます。通知の表示を許可すると、updateSubscriptionOnServer() が実行され、通知が表示されます。
また、現在地を取得する initWatchPosition() が実行され、現在地が更新されるたびに、initQuery() が呼び出され、現在地付近の店舗がないかクエリを行います。
もう一つ、[シミュレーションを実行] 機能を追加しています。これは、Mock-geolocation という位置情報を疑似するライブラリを使用して、現在地を東京タワー周辺に疑似しています。
東京タワー周辺には3つの店舗があるので、ジオフェンスに入るとどのように通知が届くのかをみることができます。
プッシュ通知は、Service Worker を使用するので、アプリを開いていなくても使用できますが、現在地の取得はバックグラウンドで実行できないので、今回は、Service Worker のメリットを実感することができませんでした。
サーバー側で PushSubscription を管理して、ユーザー別に通知内容を変えるなど、いろいろな使い方ができるのではないかと思います。
サンプルを参考に、ぜひお試しください。