前回までの連載で、
Webアプリの概要
今回作成するWebアプリは、
また、
![図1 Odometer 図1 Odometer](/assets/images/dev/serial/01/chrome-web-store/0004/thumb/TH800_001.jpg)
Webアプリの構成
Webアプリを構成するファイルを以下のように配置します。
![ファイル構成](/assets/images/dev/serial/01/chrome-web-store/0004/thumb/TH400_002.png)
{
"name": "Odometer",
"description": "距離計",
"version": "0.1",
"app": {
"launch": {
"local_path": "main.html"
}
},
"icons": {
"16": "icon_16.png",
"48": "icon_48.png",
"128": "icon_128.png"
},
"permissions": [
"geolocation",
"notifications"
]
}
Packaged Appとして作成するため
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>Odometer</title>
<link rel="stylesheet" href="./css/odometer.css">
<script src="http://maps.google.com/maps/api/js?sensor=true"></script>
<script src="./js/odometer.js"></script>
</head>
<body>
<div id="map-container">
<div id="map"></div>
</div>
<div id="data-container">
<form id="search">
<input type="text" id="address" placeholder="住所など">
<input type="submit" value="検索">
</form>
<p>地図をクリックして目的地を設定してください</p>
<section id="distance-container">
<h1>- 目的地までの距離 - </h1>
<p style="">約 <span id="distance">0</span> km<br>
<input type="button" value="自動更新開始" id="auto-update"></p>
</section>
<section>
<h1>- 目的地 -</h1>
<table>
<tr>
<td width="80px">緯度</td>
<td width="200px" id="dest-latitude"></td>
</tr>
<tr>
<td>経度</td>
<td id="dest-longitude"></td>
</tr>
</table>
<input type="button" value="目的地を表示" id="move-dest">
</section>
<section>
<h1>- 現在地 -</h1>
<table>
<tr>
<td width="80px">緯度</td>
<td width="200px" id="latitude"></td>
</tr>
<tr>
<td>経度</td>
<td id="longitude"></td>
</tr>
<tr>
<td>精度</td>
<td id="accuracy"></td>
</tr>
<tr>
<td>移動方向</td>
<td id="heading"></td>
</tr>
<tr>
<td>移動速度</td>
<td id="speed"></td>
</tr>
<tr>
<td>日時</td>
<td id="timestamp"></td>
</tr>
</table>
<input type="button" value="現在地を表示" id="move-current">
</section>
</div>
</body>
</html>
Geolocation API
Geolocation APIは、
位置情報の取得
まずは、
//オプション
var geoOptions = {
enableHighAccuracy: true, //高精度要求
timeout: 6000, //タイムアウト(ミリ秒)
maximumAge: 0 //キャッシュ有効期限(ミリ秒)
}
if (navigator.geolocation) {
//現在地の位置情報取得
navigator.geolocation.getCurrentPosition(
init, //成功時コールバック
onError, //失敗時コールバック
geoOptions //オプション
);
} else {
return;
}
現在の位置情報を取得する場合は、
/*
* 初期表示
*/
function init(position){
//地図作成
createMap(position.coords.latitude, position.coords.longitude);
//現在地情報表示
showCurrentPosition(position);
//現在地を保持
currentPos.lat = position.coords.latitude;
currentPos.lng = position.coords.longitude;
}
/*
* エラーコールバック
*/
function onError(e) {
alert(e.message + '(' + e.code + ')');
}
/*
* 現在地情報表示
*/
function showCurrentPosition(position){
//現在地を表示
//緯度
document.getElementById('latitude').textContent =
position.coords.latitude;
//経度
document.getElementById('longitude').textContent =
position.coords.longitude;
//精度
document.getElementById('accuracy').textContent =
position.coords.accuracy;
//移動方向
document.getElementById('heading').textContent =
position.coords.heading;
//移動速度
document.getElementById('speed').textContent =
position.coords.speed;
//取得日時
var dt = new Date(position.timestamp);
document.getElementById('timestamp').textContent =
dt.getFullYear() + '年' + (dt.getMonth()+1) + '月' + dt.getDate() + '日' +
dt.getHours() + '時' + dt.getMinutes() + '分' + dt.getSeconds() + '秒';
}
initメソッド内では、
プロパティ | 説明 |
---|---|
coords. |
緯度 |
coords. |
経度 |
coords. |
精度 |
coords. |
高度 ※標高ではないことに注意 |
coords. |
高度の精度 |
coords. |
移動方向 |
coords. |
移動速度 |
timestamp | 取得日時 ※本来の仕様上はDate型を返す |
目的地の設定と距離の算出
目的地の設定は、
/*
* 目的地情報表示
*/
function showDestinationPosition(lat, lng) {
//目的地を表示
document.getElementById('dest-latitude').textContent = lat;
document.getElementById('dest-longitude').textContent = lng;
}
/*
* 目的地までの距離表示
*/
function showDistance(currentPos, destPos){
//単位をkmに変換して表示
document.getElementById('distance').textContent =
Math.round(getDistance(currentPos.lat, currentPos.lng, destPos.lat, destPos.lng)) / 1000;
}
/*
* 2点間距離計算(m)
*/
function getDistance(lat, lng, dLat, dLng){
//緯度1度あたり111km、経度1度あたり91kmの概算
var h = Math.abs(dLat - lat) * 111000;
var v = Math.abs(dLng - lng) * 91000;
return Math.sqrt(Math.pow(h, 2) + Math.pow(v, 2));
}
目的地までの距離の算出については、
位置情報の自動更新
次に、
/*
* 自動更新
*/
var watchId = 0; //自動更新停止用のID
document.getElementById('auto-update').addEventListener('click', function(){
if ( watchId == 0) {
if ( destPos.lat == 0 && destPos.lng == 0 ) {
alert('目的地が設定されていません');
return;
}
//自動更新を開始する
watchId = navigator.geolocation.watchPosition(function(position){
//地図更新
updateMap(position.coords.latitude, position.coords.longitude);
//現在地情報表示
showCurrentPosition(position);
//目的地までの距離表示
currentPos.lat = position.coords.latitude;
currentPos.lng = position.coords.longitude;
showDistance(currentPos, destPos);
}, null, geoOptions);
this.value = '自動更新停止';
} else {
//以前の自動更新を停止する
navigator.geolocation.clearWatch(watchId);
watchId = 0;
this.value = '自動更新開始';
}
}, false);
実際の動きですが、
ここまでで、
メソッド | 説明 |
---|---|
getCurrentPosition(onSuccessCallback, onErrorCallback, options) | 位置情報を取得する |
watchPosition(onSuccessCallback, onErrorCallback, options) | 位置情報を継続的に取得する。戻り値として、 |
clearWatch(watchId) | 位置情報の自動更新を停止する |
オプション | 説明 |
---|---|
enableHighAccuracy | 高精度を要求する |
timeout | タイムアウト |
maximumAge | キャッシュ有効期限 |
Notification API
Odometerに新たな機能として、
Notification APIは、
デスクトップへの通知
まずは、
/*
* デスクトップへ通知
*/
function notify(title, message){
//マニフェストファイルへの記述で許可されている
if ( webkitNotifications.checkPermission() == 0 ) {
var popup = webkitNotifications.createNotification('icon_48.png', title, message);
popup.ondisplay = function(){
setTimeout(function(){
popup.cancel();
}, 5000);
};
popup.show();
} else {
//デスクトップへの通知許可を要求する
webkitNotifications.requestPermission();
}
}
webkitNotifications.
もし、
デスクトップへの通知用のポップアップは、
そのほかにも、
//通知済みの距離を保持
var notified = {};
~省略~
//自動更新を開始する
watchId = navigator.geolocation.watchPosition(function(position){
//地図更新
updateMap(position.coords.latitude, position.coords.longitude);
//現在地情報表示
showCurrentPosition(position);
//目的地までの距離表示
currentPos.lat = position.coords.latitude;
currentPos.lng = position.coords.longitude;
showDistance(currentPos, destPos);
//デスクトップに通知
//目的地までの距離を取得してkm単位に変換
var distance = Math.round(getDistance(currentPos.lat, currentPos.lng, destPos.lat, destPos.lng)) / 1000;
//距離によってしきい値を変える
var threshold = 0;
if ( distance < 1 ) {
//1km未満は、200mごとに通知
threshold = 0.2;
} else if (distance < 10 ) {
//10km未満は、1kmごとに通知
threshold = 1;
} else {
//10km以上は、10kmごとに通知
threshold = 10;
}
//一度通知した距離は再通知しない
var notifiedKey = Math.floor(distance / threshold) * threshold;
if ( !notified[notifiedKey] ) {
notify('目的地までの距離', '約 ' + distance + ' km');
notified[notifiedKey] = true;
}
}, null, geoOptions);
位置情報が更新されるたびに通知されるのは冗長ですので、
![画像](/assets/images/dev/serial/01/chrome-web-store/0004/thumb/TH400_003.jpg)
これで、
メソッド | 説明 |
---|---|
checkPermission() | デスクトップへの通知に対する許可、 |
requestPermission() | デスクトップへの通知の許可を求める |
createNotification(iconURL, title, message) | 通知用のポップアップを作成する |
createHTMLNotification(htmlURL) | 通知用のポップアップをHTMLから作成する |
メソッド | 説明 |
---|---|
show() | デスクトップへ通知する |
cancel() | デスクトップへの通知をキャンセルする |
onclick | 通知ポップアップのクリックイベントのコールバック |
onclose | 通知ポップアップの閉じるイベントのコールバック |
ondisplay | 通知ポップアップの表示イベントのコールバック ※ドラフト仕様では、 |
onerror | エラー時のコールバック |
まとめ
今回は、
- Odometer
(crxファイル) [14. 6KB]
- ※
crxファイルはzipファイルですので、
右クリックからのダウンロード後に拡張子をzipに変えていただければ中身を参照できます
また、
参考
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>Odometer</title>
<link rel="stylesheet" href="./css/odometer.css">
<script src="http://maps.google.com/maps/api/js?sensor=true"></script>
<script src="./js/odometer.js"></script>
</head>
<body>
<div id="map-container">
<div id="map"></div>
</div>
<div id="data-container">
<form id="search">
<input type="text" id="address" placeholder="住所など">
<input type="submit" value="検索">
</form>
<p>地図をクリックして目的地を設定してください</p>
<section id="distance-container">
<h1>- 目的地までの距離 - </h1>
<p style="">約 <span id="distance">0</span> km<br>
<input type="button" value="自動更新開始" id="auto-update"></p>
</section>
<section>
<h1>- 目的地 -</h1>
<table>
<tr>
<td width="80px">緯度</td>
<td width="200px" id="dest-latitude"></td>
</tr>
<tr>
<td>経度</td>
<td id="dest-longitude"></td>
</tr>
</table>
<input type="button" value="目的地を表示" id="move-dest">
</section>
<section>
<h1>- 現在地 -</h1>
<table>
<tr>
<td width="80px">緯度</td>
<td width="200px" id="latitude"></td>
</tr>
<tr>
<td>経度</td>
<td id="longitude"></td>
</tr>
<tr>
<td>精度</td>
<td id="accuracy"></td>
</tr>
<tr>
<td>移動方向</td>
<td id="heading"></td>
</tr>
<tr>
<td>移動速度</td>
<td id="speed"></td>
</tr>
<tr>
<td>日時</td>
<td id="timestamp"></td>
</tr>
</table>
<input type="button" value="現在地を表示" id="move-current">
</section>
</div>
</body>
</html>
#map-container, #data-container {
float: left;
}
#data-container {
padding: 10px;
}
#map {
width: 600px;
height: 500px;
}
h1 {
font-size: 1em;
}
#distance-container p {
font-size: 2em;
margin-top: 5px;
}
document.addEventListener('DOMContentLoaded', function(){
//現在地
var currentPos = {
lat: 0,
lng: 0
}
//目的地
var destPos = {
lat: 0,
lng: 0
}
//通知済みの距離を保持
var notified = {};
//オプション
var geoOptions = {
enableHighAccuracy: true, //高精度要求
timeout: 6000, //タイムアウト(ミリ秒)
maximumAge: 0 //キャッシュ有効期限(ミリ秒)
}
if (navigator.geolocation) {
//現在地の位置情報取得
navigator.geolocation.getCurrentPosition(
init, //成功時コールバック
onError, //失敗時コールバック
geoOptions //オプション
);
} else {
return;
}
/*
* 初期表示
*/
function init(position){
//地図作成
createMap(position.coords.latitude, position.coords.longitude);
//現在地情報表示
showCurrentPosition(position);
//現在地を保持
currentPos.lat = position.coords.latitude;
currentPos.lng = position.coords.longitude;
}
/*
* エラーコールバック
*/
function onError(e) {
alert(e.message + '(' + e.code + ')');
}
/*
* 現在地情報表示
*/
function showCurrentPosition(position){
//現在地を表示
//緯度
document.getElementById('latitude').textContent =
position.coords.latitude;
//経度
document.getElementById('longitude').textContent =
position.coords.longitude;
//精度
document.getElementById('accuracy').textContent =
position.coords.accuracy;
//移動方向
document.getElementById('heading').textContent =
position.coords.heading;
//移動速度
document.getElementById('speed').textContent =
position.coords.speed;
//取得日時
var dt = new Date(position.timestamp);
document.getElementById('timestamp').textContent =
dt.getFullYear() + '年' + (dt.getMonth()+1) + '月' + dt.getDate() + '日' +
dt.getHours() + '時' + dt.getMinutes() + '分' + dt.getSeconds() + '秒';
}
/*
* 目的地情報表示
*/
function showDestinationPosition(lat, lng) {
//目的地を表示
document.getElementById('dest-latitude').textContent = lat;
document.getElementById('dest-longitude').textContent = lng;
}
/*
* 目的地までの距離表示
*/
function showDistance(currentPos, destPos){
//単位をkmに変換して表示
document.getElementById('distance').textContent =
Math.round(getDistance(currentPos.lat, currentPos.lng, destPos.lat, destPos.lng)) / 1000;
}
/*
* 2点間距離計算(m)
*/
function getDistance(lat, lng, dLat, dLng){
//緯度1度あたり111km、経度1度あたり91kmの概算
var h = Math.abs(dLat - lat) * 111000;
var v = Math.abs(dLng - lng) * 91000;
return Math.sqrt(Math.pow(h, 2) + Math.pow(v, 2));
}
/*
* 自動更新
*/
var watchId = 0; //自動更新停止用のID
document.getElementById('auto-update').addEventListener('click', function(){
if ( watchId == 0) {
if ( destPos.lat == 0 && destPos.lng == 0 ) {
alert('目的地が設定されていません');
return;
}
//通知済みの距離をリセット
notified = {};
//自動更新を開始する
watchId = navigator.geolocation.watchPosition(function(position){
//地図更新
updateMap(position.coords.latitude, position.coords.longitude);
//現在地情報表示
showCurrentPosition(position);
//目的地までの距離表示
currentPos.lat = position.coords.latitude;
currentPos.lng = position.coords.longitude;
showDistance(currentPos, destPos);
//デスクトップに通知
//目的地までの距離を取得してkm単位に変換
var distance = Math.round(getDistance(currentPos.lat, currentPos.lng, destPos.lat, destPos.lng)) / 1000;
//距離によってしきい値を変える
var threshold = 0;
if ( distance < 1 ) {
//1km未満は、200mごとに通知
threshold = 0.2;
} else if (distance < 10 ) {
//10km未満は、1kmごとに通知
threshold = 1;
} else {
//10km以上は、10kmごとに通知
threshold = 10;
}
//一度通知した距離は再通知しない
var notifiedKey = Math.floor(distance / threshold) * threshold;
if ( !notified[notifiedKey] ) {
notify('目的地までの距離', '約 ' + distance + ' km');
notified[notifiedKey] = true;
}
}, null, geoOptions);
this.value = '自動更新停止';
} else {
//以前の自動更新を停止する
navigator.geolocation.clearWatch(watchId);
watchId = 0;
this.value = '自動更新開始';
}
}, false);
/*
* デスクトップへ通知
*/
function notify(title, message){
//マニフェストファイルへの記述で許可されている
if ( webkitNotifications.checkPermission() == 0 ) {
var popup = webkitNotifications.createNotification('icon_48.png', title, message);
popup.ondisplay = function(){
setTimeout(function(){
popup.cancel();
}, 5000);
};
popup.show();
} else {
//デスクトップへの通知許可を要求する
webkitNotifications.requestPermission();
}
}
/*
* Google Maps
*/
var map,
marker;
var mapOptions = {
zoom: 13,
mapTypeId: google.maps.MapTypeId.ROADMAP
};
/*
* 地図作成
*/
function createMap(lat, lng) {
//地図作成
var infowindow = new google.maps.InfoWindow(),
latLng = new google.maps.LatLng(lat, lng);
map = new google.maps.Map(document.getElementById("map"), mapOptions);
//マーカー作成
marker = new google.maps.Marker(
{
title: '現在地',
position: latLng,
map: map
}
);
/*
* クリックで目的地設定
*/
var destMarker = null;
google.maps.event.addListener(map, "click", function(event){
if ( destMarker ) {
destMarker.setMap(null);
}
destMarker = new google.maps.Marker(
{
title: '目的地',
position: event.latLng,
map: map
}
);
//目的地情報表示
showDestinationPosition(event.latLng.lat(), event.latLng.lng());
//目的地までの距離表示
destPos.lat = event.latLng.lat();
destPos.lng = event.latLng.lng();
showDistance(currentPos, destPos);
//通知済みの距離をリセット
notified = {};
});
map.setCenter(latLng);
infowindow.open(map);
}
/*
* 地図更新
*/
function updateMap(lat, lng) {
var latLng = new google.maps.LatLng(lat, lng);
//マーカー作成
if ( marker ) {
marker.setMap(null);
}
marker = new google.maps.Marker(
{
title: '現在地',
position: latLng,
map: map
}
);
map.setCenter(latLng);
}
/*
* 住所検索
*/
var geocoder = new google.maps.Geocoder();
document.getElementById('search').addEventListener('submit', function(event){
//デフォルトのsubmit動作をキャンセル
event.preventDefault();
var addr = document.getElementById('address').value;
if ( !addr ) {
return;
}
//Geocoding APIで住所から座標を取得する
geocoder.geocode({ 'address': addr}, function(results, status) {
if (status == google.maps.GeocoderStatus.OK) {
//最初の候補を表示する
map.setCenter(results[0].geometry.location);
} else {
alert('検索できませんでした');
}
});
}, false);
/*
* 目的地表示
*/
document.getElementById('move-dest').addEventListener('click', function(){
map.setCenter(new google.maps.LatLng(destPos.lat, destPos.lng));
}, false);
/*
* 現在地表示
*/
document.getElementById('move-current').addEventListener('click', function(){
map.setCenter(new google.maps.LatLng(currentPos.lat, currentPos.lng));
}, false);
}, false);