PostGISをLaravelで操作する

# はじめに

- [2019/03/13] **[laravel-geoについて追記しました](https://qiita.com/myoshioka/items/f8a9a7f0f1da1b1853fb#%E8%BF%BD%E8%A8%9820190313)**
- LaravelでPostGISを利用する際に調査した拡張ライブラリとその利用方法を備忘録として残しておきます。



# ライブラリ

- 色々と調べてみたところ、以下の2つのライブラリがよさそうで実際に利用してみました。
- 結論からいうと**laravel-geo**を利用することにしました。

## laravel-postgis

- [laravel-postgis](https://github.com/njbarrett/laravel-postgis)
- starの数も多く使いやすそうで、はじめはこちらを利用しようとしていました。
    - ただ、githubのREADMEにもありますが**現状だと図形の演算機能が利用できないようです**。
    - Geometry関連の検索がしたいというのがPostGISの利用の理由なのでこちらのライブラリの利用は見送りました。

### 利用方法

- ほぼgithubのREADMEのとおりですが、以下のように利用します。

- インストール

```bash
composer require phaza/laravel-postgis
```

- マイグレーション
    - デフォルトだとGEOGRAPHY型でSRIDが4326(WGS84)で定義されます。
    - 以下のようにして独自に定義も出来ます。
        - `$table->point('location', 'GEOMETRY', 27700);`

```php

use Illuminate\Database\Migrations\Migration;
use Phaza\LaravelPostgis\Schema\Blueprint;

class CreateGeoTable extends Migration {

public function up()
    {
        Schema::create('geometry_sample', function(Blueprint $table)
        {
            $table->increments('id');
            $table->string('name');
            $table->point('geom1');  //Geometry型(point)
            $table->polygon('geom2'); //Geometry型(polygon)
            $table->timestamps();
        });
    }
```

- モデル定義

```php
class GeometrySample extends Model
{
    use PostgisTrait;
    protected $postgisFields = [
        'geom1',
        'geom2'
    ];
    
    protected $postgisTypes = [
        'geom1' => [
            'geomtype' => 'geography',
            'srid' => 4326
        ],
        'geom2' => [
            'geomtype' => 'geography',
            'srid' => 4326
        ]
    ]
}
```

- 利用
    - 空間検索などのPostGISの関数は未対応ですが、、Geometry型のデータをいい感じであつかえるようにしてくれます。
    - PointやLineStringなどのGeometryクラスが内部で定義されているのでこれを利用します。

```php
use Phaza\LaravelPostgis\Geometries\Point;
use Phaza\LaravelPostgis\Geometries\Polygon;

$geometryData = new GeometrySample();
$geometryData->name = 'hogehoge';
$geometryData->geom1 = new Point(139.55723, 35.52675);
$polygon = new Polygon(
    [
        new Point(139.55723, 35.52675),
        new Point(139.57574, 35.52675),
        new Point(139.55723, 35.50841)
    ]
);
$geometryData->geom2 = $polygon;
$geometryData->save();

```


## laravel-geo

- [laravel-geo](https://github.com/eleven-lab/laravel-geo)
- こちらは以下のような図形演算が利用可能です。
    - intersection
    - difference
    - contains
    - intersects
    - touches
    - overlaps
    - centroid
    - distance
    - equals
- PostGISだけでなくMySQLのGeometry型にも対応しています。

### 利用方法

- インストール

```bash
composer require elevenlab/laravel-geo
```

- マイグレーション
    - laravel-postgisと同じ感じで定義します。
    - Geometryデータは、GEOGRAPHY型でsridは4326(WGS84)固定で定義されるようです。(独自の定義は出来ないみたい)

```php

class CreateGeoTable extends Migration
{

    public function up()
    {
        Schema::create('geometry_sample', function(Blueprint $table)
        {
            $table->increments('id');
            $table->string('name');
            $table->point('geom1');  //Geometry型(point)
            $table->polygon('geom2'); //Geometry型(polygon)
            $table->timestamps();
        });
    }
```

- モデル定義
    - GeoModelをextendsして定義します。


```php
use ElevenLab\GeoLaravel\Eloquent\Model as GeoModel;

class GeometrySample extends GeoModel
{
    protected $table = "geometry_sample";

    protected $geometries = [
        "points" => ['geom1'],
        "polygons" =>   ['geom2']
    ];
}
```


- 利用
    - Geometryクラスはlaravel-postgisとは違い[php-ogc](https://github.com/eleven-lab/php-ogc)というライブラリを内部で利用しています。
        - Polygonオブジェクトの作り方が若干違いますが、利用方法としてはほぼ同じです。

```php
use ElevenLab\PHPOGC\DataTypes\Point;
use ElevenLab\PHPOGC\DataTypes\Polygon;

$geometryData = new GeometrySample();
$geometryData->name = 'hogehoge';
$geometryData->geom1 = new Point(139.55723, 35.52675);
$lineString = new LineString(
    [
        new Point(139.55723, 35.52675),
        new Point(139.57574, 35.52675),
        new Point(139.55723, 35.50841)
    ]
);
$polygon = new Polygon([$lineString]);


$geometryData->geom2 = $polygon;
$geometryData->save();

```

- 図形演算のクエリもEloquentのQueryBuilderを使って利用出来ます。
    - たの利用可能な演算は[こちら](https://github.com/eleven-lab/laravel-geo#builds-queries)に記載されています。

```php

GeometrySample::select('name')->whereContains('geom2', new Point(139.55723, 35.52675));
```

### 追記[2019/03/13]

- 現状だとlaravel-geoはLaravelの現在のLTSである**v5.5の機能に一部対応していないようです**。
- [こちら](https://github.com/karomap/laravel-geo)のリポジトリにforkされたものが5.4に対応と記載がありますが、確認したところ**5.5では一部の機能でエラーが発生しました**。(5.4の動作は未確認です)
    - 上記のリポジトリで、手元で確認したところ以下が未対応のようでした。
        - **RefreshDatabase Traitに未対応**
        - **SchemaファサードのhasColumnメソッドの変更に未対応**
            - [こちら](https://github.com/laravel/framework/pull/19553)のPostgresBuilderのgetColumnListingメソッドの変更PRに未対応のため
            - masterブランチでは対応されているようですが、現状最新の1.1.1のリリースブランチではこの変更が取り込まれていません。
- 全てを確認はしていないので、上記以外にも5.5未対応の機能がある可能性はあるので、**図形演算を利用しないのであればlaravel-postgisを利用したほうが良いかと思います**。

# まとめ

- 緯度経度などのデータを扱う場合、ここにあげたライブラリ使わずに独自で実装するにしても結局は内部でGeometryクラスを独自実装することになると思うので、それだけでもこれらのライブラリの利用価値はあるかと思います。
- PostGISの全ての関数に対応しているわけでは無いですが、図形演算処理ができればそこそこの要件には足りるかと思います。
    - 駅から＊＊m以内にあるコンビニを検索とか
- ただし、どうしても要件を満たさない場合は`DB::raw`とかで直接SQL書くことになると思います。。