CakePHP2+PostgreSQLでGeoデータを簡単に扱う!?

@tkengoさんから引き続き CakePHP Advent Calendar 14日目の記事です。

深く考えず参加申し込みをしたらダブルヘッダーになってしまいヒーヒー言っているわたなべです(^^;

この記事はCakePHP2系の記事です。


明日にはこんなイベントも開催されるようで、最近は位置情報を扱う機会も多いのではないかと思います。

先月くらいに位置情報に関して色々試していて、CakePHPから扱う場合にいつもと同じように扱いたいなぁということでPostgres Datasourceを拡張してみたので解説したいと思います。

PostgreSQLで扱える位置情報の種類

PostgreSQLで扱える位置情報関連のデータ型には以下のようなものがあります。

型名 表現
point 平面における座標点
line 無限の直線
lseg 有限の線分
box 矩形
path 閉経路
path 開経路
polygon 多角形
circle

幾何データ型

今回は携帯での位置情報を簡単に扱えるようにという目標なのでこの中からデータ型としては 'point'型と'box'型のサポートをしました。

位置情報関連の演算子

下記のマニュアルページを見ていただければ分かる通り、たくさんの演算子がサポートされています。

幾何データ演算子

今回はこの中から以下の演算子をサポートを追加しました。

演算子 説明
@ 含む、または境界上
~= 同等か?
<-> 距離

スキーマ定義

今回の追加機能のテスト用に作成したFixtureは下記になります。

<?php
class PostgresGeoFixture extends CakeTestFixture {

/**
 * name property
 *
 * @var string 'PostgresGeo'
 */
	public $name = 'PostgresGeo';

/**
 * fields property
 *
 * @var array
 */
	public $fields = array(
		'id' => array('type' => 'integer', 'key' => 'primary'),
		'loc' => array('type' => 'point', 'null' => false),
		'rect' => array('type' => 'box', 'null' => false),
	);
}

point型の locとbox型の rect フィールドを作成しています。

使い方

データを保存する場合は、以下の様に座標データを配列形式で設定すればOKです。
box型の場合は左上の座標と、右下の座標を配列で渡します。

<?php

$data = array(
  'loc'	 => array(10.1,10.1),
  'rect' => array(array(10.1,10.2), array(100.1,100.2)),
);

$Model->create();
$Model->set($data);
$result = $Model->save();

特定の円の中に含まれているデータを検索する場合はこんな感じです。

<?php
// @ operator
// compare to circle
$conditions = array(
  'loc @' => array('circle' => array('point'=> array(10.1,10.1), 100)),
);
// order by distance
$order = array(
  'loc <-> point(10.1,10.1)'
);

$result = $model->find('all', array('conditions'=>$conditions, 'order'=>$order));

この例では中心座標 10.1,10.1、半径100の範囲内に含まれるデータが取得できます。また、<->演算子で並べ替えることで、中心座標に近い順に並べ替えることが可能です。

Postgresデータソースのテストを書いてあるのでそちらも見ていただけると、もう少しイメージが湧くかと思います。

誤算

当初この修正を始めたときは、PostgreSQLデータソース内部ですべて解決できそうな気がした(^^; ので、CakePHP2をforkして作業を始めたのですが、実際に作業を進めて見た結果、DboSource(データベース関連データソースの共通クラス)にも手を入れないと実現ができませんでした。

さすがにDatasource固有の演算子サポートのためにDboSourceに手を入れる修正はコアに取り込んでもらえないなぁと思い、今後この機能をプラグイン化できないか調査をしているところです。

実際に動くコードは、GitHubに上がっているのでもし興味がある方は試してみてください。結構便利だと思いますよ。

明日は @scriptwork さんです。よろしくお願いします!