2010年8月アーカイブ
observerとは「観察者」という意味。
Observerパターンは観察対象の状態が変化すると、観察者に
対して変化が通知される。
状態変化に応じた処理を記述するときに有効なパターン。
Observerパターンのクラス図

Observerパターンのサンプルプログラム
今回のサンプルプログラムは結城本のサンプルをPHPにした感じです。数を生成するオブジェクトを観察者が観察して、生成された数字を観察者
毎に異なる形式で表示します。
DigitObserverは数字で表示します。
GraphObserverは「*」の棒グラフで表示します。
・Observer側(Observer.php)
// interface Observer
interface Observer {
public function update($generator);
public function getName();
}
// DigitObserver Class
// 数字で表示します。
class DigitObserver implements Observer {
public function update($generator) {
echo "DigitObserver:" . $generator->getNumber() . "\n";
}
public function getName() {
return 'DigitObserver';
}
}
// GraphObserver Class
// 「*」で棒グラフみたいに出力します。
class GraphObserver implements Observer {
public function update( $generator ) {
echo " GraphObserver: ";
for( $i = 0; $i < $generator->getNumber(); $i++ ) {
echo "*";
}
echo "\n";
}
public function getName() {
return 'GraphObserver';
}
}
・Subject側(Generator.php)
// Abstract NumberGenerator
// 抽象クラス
abstract class NumberGenerator {
private $observers = array();
public function addObserver( $observer ) {
$this->observers[ $observer->getName() ] = $observer;
}
public function deleteObserver( $observer ) {
unset( $this->observers[ $observer->getName() ] );
}
public function notifyObservers() {
foreach( $this->observers as $val ) {
$val->update( $this );
}
}
abstract public function getNumber();
abstract public function execute();
}
// RandomNumberGenerator Class
// 具象クラス
class RandomNumberGenerator extends NumberGenerator {
private $number;
public function __construct() {
srand(time());
}
public function getNumber() {
return $this->number;
}
public function execute() {
for ($i = 0; $i < 20; $i++) {
$this->number = rand(0, 49);
$this->notifyObservers();
}
}
}
※Iteratorパターンは使ってません・・・。
・mainプログラム(main.php)
require "Observer.php"; require "Generator.php"; // 具象クラス $generator = new RandomNumberGenerator(); // 観察者1 $observer1 = new DigitObserver(); // 観察者2 $observer2 = new GraphObserver(); // 観察対象に観察者1を追加 $generator->addObserver($observer1); // 観察対象に観察者2を追加 $generator->addObserver($observer2); // 実行 $generator->execute();実行結果
$ php main.php DigitObserver:34 GraphObserver: ********************************** DigitObserver:1 GraphObserver: * DigitObserver:14 GraphObserver: ************** DigitObserver:9 GraphObserver: ********* DigitObserver:24 GraphObserver: ************************ DigitObserver:36 GraphObserver: ************************************ DigitObserver:18 GraphObserver: ****************** DigitObserver:42 GraphObserver: ****************************************** DigitObserver:12 GraphObserver: ************ DigitObserver:36 GraphObserver: ************************************ DigitObserver:35 GraphObserver: *********************************** DigitObserver:8 GraphObserver: ******** DigitObserver:13 GraphObserver: ************* DigitObserver:22 GraphObserver: ********************** DigitObserver:6 GraphObserver: ****** DigitObserver:36 GraphObserver: ************************************ DigitObserver:20 GraphObserver: ******************** DigitObserver:39 GraphObserver: *************************************** DigitObserver:33 GraphObserver: ********************************* DigitObserver:40 GraphObserver: ****************************************
解説
Observerインターフェースは「観察者」を表現するインターフェースで具体的な
観察者はこのインターフェースを実装します。
NumberGeneratorがupdateメソッドを呼び出します。
updateメソッドはNumberGeneratorが内容の変更をObserverに通知するための
メソッドです。
NumberGeneratorは数を生成する抽象クラスです。
実際の数の生成と数を取得する部分はサブクラスに実装してほしいので
抽象メソッドになってます。
observersプロパティはNumGeneratorを観察しているObserverたちを保存
しています。
addObserverはObserverを追加すメソッド、deleteObserverはObserverを削除
するメソッドです。
notifyObserversメソッドはObserver全員に更新を伝えるメソッドです。
※addObserverでobserversプロパティに登録されているObserver達に通知。
Observerパターンでの役割分担
・Subject(被験者)役
サンプルプログラムでのNumberGeneratorクラスに相当
・ConcreteSubject(具体的な被験者)役
サンプルプログラムでのRandomNumberGeneratorクラスに相当
・Observer(観察者)役
サンプルプログラムでのObserverインターフェースに相当
・ConcreteObserver(具体的な観察者)役
サンプルプログラムでのDigitObserverクラスやGraphObserverクラスに相当
メリット
・具体的な被験者クラスは現在自分を観察しているクラスについて気にする必要が無い。
・具体的な観察者クラスも自分の観察しているについて気にする必要が無い。
・通知したい観察者だけを被験者が登録すればいい。
・観察対象がイベントの監視を一括して行えるように監視義務を委譲できる。
・将来新たにObsesrverを追加する必要が出てきても、Subjectを変更する必要が無い。
デメリット
・updateメソッドが呼ばれる順番が変わっても問題が起きない実装にしなければならない
※Observerの行為がSubjectに影響を与える場合は互いに呼び出しが続く可能性がある
⇒Observer役に「現在Subject役から通知されている最中がどうか」をあらわすフラグを持たせるなどで回避
実装する時の順番
1.Observerの振る舞いを統一する
2.オブザーバに自らを登録させる
3.イベント発生の際、オブザーバに通知する
4.観察対象から追加情報を取得する
Observerパターンって名前がなんか・・・
Observerパターンの解説を読んでいつも気持ち悪いなと思うところ。。
「観察者」と説明しているのに観察対象者側がどの観察者に観察させるかを
選んでいるところ。
観察というよりは変更を教えてもらってる感が非常に強い。
ObserverパターンのObserverは能動的ではなくて受動的。
で、どうもObserverパターンをいう呼び名以外にPublish-Subscribeパターン
と呼ばれることもあるらしい。「Publish」発行と「subscribe」購読ということみたい。
こっちの呼び名のほうがしっくりくると思うんだけど、みんなはどうなんだろ?
MVCとか
Model/View/ControllerのModelとViewの関係はObserverパターンの
Subject役とObserver役の関係に対応しているみたい。
ViewがModelをどのようにみせるか?という役割をもっているのであれば
Modelが変わったんだからViewも変わるはずという意味では納得。
ギコ猫とObserverパターン
位置情報関連をまったく知らないのでWEB+DB PRESS vol.57を読んで現在の主流を
把握する。
以下メモ。
※自分がdocomoケータイしかもって無いのでサンプルプログラム
はdocomoでしか確認してない。。
※スマートフォンの取得方法は今回は書かない。だって試せないんだもん。
店舗やランドマークなど地理空間上に重ねるポイントのことを
POI(Point of Interest)という。
POIは経年変化が激しくデータベースの維持が大変。
食べログとかはCGM(Consumer Generated Media)でメンテナンスしてる。
データベースをAPIで提供しているところも多い。
位置情報サービスを支える技術
・位置情報取得部
・POIデータベース
・サーバインターフェース
・クライアントインターフェース
位置情報取得部
現在の主流
GPS
基地局測位
Wi-Fi測位
POIデータベース
POIデータベースはPOIを登録し、検索や入出力機能を提供するため
のものです。
サーバインターフェース
POIデータベースのデータ入出力をするためのサーバ側APIです
位置情報データのやり取りはOpenGISという標準規格に沿ったものが主流
らしいけど国内のサービスでOpenGISに準拠したものは多くない。
位置情報を表現する規格は沢山あり有名どころではXML形式のGMLやGMLを元に
つくられたGoogleのKML、簡易的な地理空間情報のみ表現可能なGeoRSS、
JSON形式のGeoJSONなどがある。
クライアントインターフェース
クライアントインターフェースは、地図などでPOIデータを表現する部分。
FOSS4G
Free and Open Source Software for Geospatial の略
フリーのオープンソースの地理空間情報ソフトウェアの総称。
PostGIS OpenLayersなど色々
緯度経度について
緯度(latitude)は赤道を0度として南北へそれぞれ90度で表現したものです。
数字で表現する場合北を+南を-とします。
経度(longitude)はグリニッジ天文台跡を南北へ通る子午線を0度とし、東西へ
それぞれ180度で表現したものです。
数字で表現する場合、東を+西を-とします。
緯度経度の一般的な表記として分、秒を60進で表現した度分秒(DMS)単位と、
全てを10進法で示したDegree単位とがつかわれます。
最近はDegreeが多いようです。
DMS単位
・東経139度43分0秒88、北緯35度38分50秒644
・東経139°43′0″88、北緯35°38′50″644
・+139.43.0.88、+35.38.50.644
Degree単位
・東経139.716911度、北緯35.647401度
・+139.716911、+35.647401
同じ場所でも利用する測地系(datum)によって緯度経度の値が変わる。
多くの測地系があるけど日本で気にする必要があるのは「日本測地系」と
「世界測地系(WGS84)」
日本では2002年4月1日まで日本測地系が使われてきた経緯がありまだサービス
として利用しているものもある。
日本測地系をそのまま世界測地系に利用してしまうと400メートル位ズレが
発生する。そのため利用するためには変換する必要がある。
これを測地系変換という。
GPS
見晴らしがよければかなり正確な位置が取得できるが、測位に時間がかかる。
屋内では測位出来ないこともある。純粋にGPS衛星のみ利用する場合と
基地局情報も利用して測位情報を補完するAGPS(Assisted GPS)の2種類ある。
ナビとかはGPSを使った正確な位置情報が必要になる。
基地局測位
GPSが搭載されていない端末でも、基地局情報を元にした三角測位によって
大体の場所を把握できる。
GPS搭載端末でも基地局測位のみを利用することもできる。
場所とか基地局の数とかで大幅に誤差が出ることもある。
その代わりに短時間で位置情報を取得できる。
ある程度広いエリアのタウン情報を表示するような位置情報サイトの場合は
これで十分かも。取得時間という面でユーザビリティの観点からあえてこちらを
採用することもある。
Wi-Fi測位
iPhoneやAndroidなどのスマートフォンで利用されている技術です。
Wi-FiのSSID(Service SetID)及び電波状況と経度緯度を組み合わせた
データベースを利用し位置を割り出しています。
GPSに比べて電池消費量や速度で有利です。
ガラケーでの位置情報取得
いずれのキャリアでも特定のフォーマットでGET/POSTアクセスを行うと、キャリア
のゲートウェイから緯度経度付きのクエリがサーバに送信される。
ドコモ
[GPS]
ドコモでGPSを利用して緯度経度を取得するには<a>タグやフォームに
「lcs」と記述する
サンプル - 例)
<a href="http://example.com/xxx" lcs>位置情報取得</a>
結果 - 例)
GET /xxx?lat=%2b35.00.35.6008&lon=%2B135.41.35.600
&geo=WGS84&x-acc=3
lat/lonが緯度/経度、geoが測地系(WGS84は世界測地系)、x-accが測位レベル
測位レベルは以下
3 ⇒ 水平誤差50m未満
2 ⇒ 水平誤差50m以上300m未満
1 ⇒ 水平誤差300m以上
詳細はドコモのページで。
[オープンiエリア(基地局測位)]
GPSを利用せずに基地局情報を元にした測位結果を返す。
GPSより高速かつすべての端末で利用できるが精度はかなり大雑把。
(周辺の基地局の密集度などによって精度が変わる)
<a>タグやフォームでの記述例
サンプル - 例)
<a href="http://w1m.docomo.ne.jp/cp/iarea?encode=OPENAREACODE&msn=OPENAREAKEY&posinfo=1&nl=http://example.com/xxx">
位置情報取得</a>
結果 - 例) nlで指定したURLに以下の位置情報が送信される
AREACODE:エリアコード
LAT:+dd.mm.ss.sss (緯度)
LON:+ddd.mm.ss.sss (経度)
GEO:wgs84(測地系)
XACC: 1 or 2 or 3 (測位レベル)
POSINFO:1(指定したposinfoの値)
arg1で指定した名前:arg1で指定した値
arg2で指定した名前:arg2で指定した値
au
[EZナビ(GPS)]
<a>タグやフォームでdevice:gpsone?url=URLと指定することで指定したURLに
位置情報が送信される。
サンプル - 例)
<a href="device:gpsone?url=http://example.com/xxx&ver=1&datum=0&unit=0">
位置情報取得</a>
varの値は固定らしい。
datumは測地系で0が世界測地系で1が日本測地系。
unitは緯度経度の単位で0がDMS単位、1がGegree単位。
結果 - 例)
GET /xxx?ver=1&datum=0&unit=0&lat=%2B35.12.34.56&lon=%2B135.12.34.56
&alt=50&time=20020806134715&smaj=100&smin=100&vert=100
&majaa=60&fm=0
altは海抜高度(メートル)、timeは位置情報を取得した時間(日本時間YYYYMMDDHHMMSS)、
smajは水平誤差(メートル)、fmは測位結果の精度に関する情報が0?5の値で
入ります。
smin、vert、majaaはそれぞれ短軸半径誤差、高度誤差、誤差楕円長軸角度らしいけど正確な値は入ってこないらしい・・・。
詳細はauのページで。
[簡易位置情報(基地局測位)]
サンプル - 例)
<a href="device:location?url=http://example.com/xxx">位置情報取得</a>
結果 - 例)
GET /xxx?datum=tokyo&unit=gms&lat=35.43.25.38&lon=135.43.25.38
datum(測地系)にtokyoと入ってくるが必ず世界測地系が返ってくるらしいので
無視しないといけないらしい・・・。
auの注意点は独自パラメータを引き渡せないところ。
EZ番号とかいうのをキーにして一旦DBとかにセッション情報を格納したり、URLを
動的にしてセッションIDを埋め込むとか対策が必要。
ソフトバンク
ソフトバンクはやり方が共通。
[GPS,基地局測位共通]
<a>タグやフォームでlocation:auto?url=URLのように指定すればいいらしい。
サンプル - 例)
<a href="location:auto?url=http://example.com/xxx">位置情報</a>
autoの部分をcellに変えると簡易位置情報のみ、gpsに変えるとGPSのみを利用する
ということみたい。
結果 - 例)
GET /xxx?pos=N35.40.53.00E139.45.57.97&geo=wgs84&x-acr=1
緯度経度はposにDMS単位で入ってくる。
詳細はソフトバンクのページで。
汎用ライブラリ
いままで見てきたようにキャリアごとのコードが違ってめんどくさい。
で、PHPにはPEAR::Net_UserAgent_Mobile_GPS、phpgeomobilejpなど
キャリアの差を吸収してくれるライブラリがあるらしい。
ここまで書いてみてGPS搭載端末じゃなくても位置情報取れるんだ!
ってことを学びました。
ではとりあえず素の実装で位置情報を取得するプログラムを書いてみる。
素のプログラム
index.php
<?php
$HTTP_USER_AGENT = $_SERVER['HTTP_USER_AGENT'];
function IsDoCoMo() {
global $HTTP_USER_AGENT;
if( ereg("DoCoMo", $HTTP_USER_AGENT) ) {
return true;
} else {
return false;
}
}
function IsEZ() {
global $HTTP_USER_AGENT;
if( ereg("KDDI", $HTTP_USER_AGENT) ) {
return true;
} else {
return false;
}
}
function IsSoftBank() {
global $HTTP_USER_AGENT;
if( ereg("Vodafone", $HTTP_USER_AGENT) ) {
return true;
} else if( ereg("SoftBank", $HTTP_USER_AGENT) ) {
return true;
} else {
return false;
}
}
?>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>位置情報サンプル</title>
</head>
<body>
<p>携帯の位置情報を取得するよ!</p>
<?php if( IsDoCoMo() ) {?>
<form action="http://w1m.docomo.ne.jp/cp/iarea" method="post">
<input type="hidden" name="ecode" value="openareacode" />
<input type="hidden" name="msn" value="openareakey" />
<input type="hiddne" name="nl" value="http://xxxxx.com/mobile/
geo.php" />
<input type="hidden" name="arg1" value="param=hogehoge"/>
<input type="hidden" name="arg2" value="guidlax=
<?php echo $HTTP_X_DCMGUID?>" />
<input type="hidden" name="posinfo" value="1" />
<input type="submit" name="ok" value="位置情報取得!" />
</form>
<?php } elseif( IsEZ() ) {?>
<a href="device:gpsone?url=http://xxxxx.com/mobile/geo.php&ver=1
&datum=0&unit=0">位置情報取得!</a>
<?php } elseif( IsSoftBank() ) {?>
<a href="location:auto?url=http://xxxxx.com/mobile/geo.php
¶m=hoge">
位置情報取得!</a>
<?php } else {?>
<p>携帯じゃないよ。</p>
<?php echo $_SERVER['REQUEST_URI'] ?>
<?php }?>
</body>
</html>
geo.php
<?php
$HTTP_USER_AGENT = $_SERVER['HTTP_USER_AGENT'];
function IsDoCoMo() {
global $HTTP_USER_AGENT;
if( ereg("DoCoMo", $HTTP_USER_AGENT) ) {
return true;
} else {
return false;
}
}
function IsEZ() {
global $HTTP_USER_AGENT;
if( ereg("KDDI", $HTTP_USER_AGENT) ) {
return true;
} else {
return false;
}
}
function IsSoftBank() {
global $HTTP_USER_AGENT;
if( ereg("Vodafone", $HTTP_USER_AGENT) ) {
return true;
} else if( ereg("SoftBank", $HTTP_USER_AGENT) ) {
return true;
} else {
return false;
}
}
$LAT = $_POST['LAT'];
$LON = $_POST['LON'];
if( IsDoCoMo() or IsEZ() ) {
if( ($LAT) and ($LON) ) {
$lat = str_replace( "+", "", trim($LAT) );
$lon = str_replace( "+", "", trim($LON) );
}
$lat = trim( $lat );
$lon = trim( $lon );
$lat = str_replace( " ", "", $lat );
$lon = str_replace( " ", "", $lon );
} else if( IsSoftBank() ) {
$pos = trim( $pos );
$pos_arr = explode( "E", $pos );
$lat = str_replace( "N", "", $pos_arr[0] );
$lon = $pos_arr[1];
} else {
$flag = 1;
}
if( $flag != 1 ) {
$lat_arr = explode( ".", $lat );
$lon_arr = explode( ".", $lon );
$lat_m = trim( $lat_arr[0] ) + trim( $lat_arr[1] ) / 60
+ ( trim( $lat_arr[2] ) + trim( $lat_arr[3] ) / 1000 ) / 3600;
$lon_m = trim( $lon_arr[0] ) + trim( $lon_arr[1] ) / 60
+ ( trim( $lon_arr[2] ) + trim( $lon_arr[3] ) / 1000 ) / 3600;
} else {
$lat_m = '34.707785';
$lon_m = '137.73778';
}
echo '緯度 -> ' . $lat_m . '<br />';
echo '経度 -> ' . $lon_m . '<br />';
echo sprintf( '<img src="http://maps.google.com/staticmap?center=%s,%s
&markers=%s,%s,redc&zoom=14&size=300x200&maptype=mobile&sensor=false
&key=key" width="300" height="200" class="imageframe"
alt="Google Static Maps" />', $lat_m, $lon_m, $lat_m, $lon_m );
?>
実行結果画面
基地局測位は誤差がでかいな・・・。
やってみた感想
実機で確認しないとわからないから作るの面倒だなと・・・。
でも逆に環境さえそろえてしまえば色々なアプリと組み合わせられる
からアイデアが広がりそうだなーといった感じ。
