Laravel + Nuxtで現在地から最寄りのイベントを取得する(Laravel API編)

# 概要
現在、個人的に開発しているサービスで、ユーザの現在地から最寄りの開催中イベントを近い順に取得を行い
プルダウンで表示を行いたい

# 環境
Laravel 5.8
Nuxt 2.5.1

# 参考サイト
[Laravel で MySQL の GEOMETRY 型のデータを扱いたかったので専用のトレイトをつくってみました](https://qiita.com/nunulk/items/bf4e2b005d4cd33d2f9b)

[[laravel]緯度経度から最寄り駅を取得](https://qiita.com/t_inoue0204/items/309637946e01321ba85e)

[laravel-mysql-spatial](https://github.com/grimzy/laravel-mysql-spatial)

# 実装方法

### laravel-mysql-spatialをインストールする

```
composer require grimzy/laravel-mysql-spatial
```

### マイグレーションファイルを作成

Laravelでは通常`$table->geometry('location')`と実装するが、`laravel-mysql-spatial`を使用する場合は`$table->point('location')`と実装する。

```php
<?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateEventsTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('events', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->unsignedBigInteger('operator_id');
            $table->unsignedBigInteger('currency_id');
            $table->string('event_name');
            $table->dateTime('opening_date')->nullable();
            $table->dateTime('end_date')->nullable();
            $table->string('prefectures'); // 都道府県
            $table->string('municipalities'); // 市町村
            $table->string('house_number'); //番地
            $table->string('building_name'); // 建物名
            $table->string('supplementary'); // 補足
            $table->point('location'); // 位置情報(緯度経度)
            $table->string('main_image_path')->nullable(); // イメージマップ画像
            $table->text('comment'); // コメント
            $table->integer('fee_division'); // 手数料区分 割合 or 固定
            $table->integer('opening_fee'); // XX% or XXXX円
            $table->timestamps();

            $table->index( 'operator_id' );
            $table->index( 'currency_id' );

            $table->foreign('operator_id')
                ->references('id')
                ->on('operators')
                ->onUpdate('cascade');

            $table->foreign('currency_id')
                ->references('id')
                ->on('currencies')
                ->onUpdate('cascade');
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('events');
    }

}

```

### モデルを作成

Eloquentで使用するために`protected $spatialFields`に対して、Geometry型のColumnを定義する。

```php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Grimzy\LaravelMysqlSpatial\Eloquent\SpatialTrait;

class Event extends Model
{
    /**
     * The attributes that are mass assignable.
     *
     * @var array
     *
     * @property \Grimzy\LaravelMysqlSpatial\Types\Point   $location
     */

    use SpatialTrait;
    
    protected $fillable = [
        'operator_id',
        'currency_id',
        'event_name',
        'opening_date',
        'end_date',
        'prefectures',
        'municipalities',
        'house_number',
        'building_name',
        'supplementary',
        'main_image_path',
        'comment',
        'fee_division',
        'opening_fee',
    ];

    protected $spatialFields = [
        'location',
    ];

    public function currency()
    {
        return $this->hasOne('App\Models\Currency', 'id');
    }
}

```


### テストデータの挿入

``` EventsTableSeeder.php
<?php

use Illuminate\Database\Seeder;

class EventsTableSeeder extends Seeder
{
    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run()
    {
        DB::table('events')->insert([
            [
                'operator_id' => '1',
                'currency_id' => '1',
                'event_name' => '興正寺マルシェ',
                'prefectures' => '愛知県',
                'municipalities' => '名古屋市昭和区',
                'house_number' => '八事本町７８',
                'building_name' => '',
                'supplementary' => '補足です',
                'location' => DB::raw("ST_GeomFromText('POINT(35.13988 136.96248)')"),
                'comment' => '「八事山興正寺」は、名古屋市昭和区にある真言宗のお寺です。
都会の中にあって美しい自然が昔からの姿で残ります。
重要文化財である五重塔がみえる入り口すぐの境内にて毎月21日に定期開催しています。
こだわりの農産物や加工食品、工芸品はもちろんペットのお散歩もできる境内ならではのペットブースもあります。',
                'fee_division' => '1',
                'opening_fee' => '10',
            ]
        ]);

        DB::table('events')->insert([
            [
                'operator_id' => '1',
                'currency_id' => '2',
                'event_name' => 'ミッドランドマルシェ',
                'prefectures' => '愛知県',
                'municipalities' => '名古屋市中村区',
                'house_number' => '名駅4丁目7-1(ミッドランドスクエア南側広場)',
                'building_name' => '',
                'supplementary' => '補足です',
                'location' => DB::raw("ST_GeomFromText('POINT(35.17000 136.88518)')"),
                'comment' => '名古屋駅前のランドタワー「ミッドランドスクエア」で名古屋駅エリア初開催。大人のラグジュアリーモールの広場ならではのちょっぴりお洒落なマルシェをお楽しみ下さい。。',
                'fee_division' => '1',
                'opening_fee' => '10',
            ]
        ]);
    }
}

```

### APIの実装
APIは現在イベントが開催中かつ、現在の位置情報をもとに最寄りの10件を取得する

```php
   /**
     * 購入者が現在参加できるイベントをコンボボックスに表示するためにイベント情報一覧を取得を行う
     * @param Request $request
     * @return \Illuminate\Http\Response
     */
    public function index(request $request){

        // バリデーションルール
        $validator = Validator::make($request->all(), [
            'latitude' => 'required|numeric',
            'longitude' => 'required|numeric',
        ]);

        // バリデーションエラーだった場合
        if ($validator->fails()) {
            return response()->json([
                'success' => false,
                'errors' => $validator->errors()->toArray()
            ], 422);
        }
        
        // 現在の開催中かつ最寄り10件のイベント情報を取得する
        $events = Event::where('end_date', '>=', Carbon::now())
            ->orderByRaw('ABS(Y(location) - ? ) + ABS(X(location) - ? )', [$request->input('latitude'), $request->input('longitude')])
            ->limit(10)
            ->get();

        return response()->json([
            'success' => true,
            'data' => $events,
        ], 200);

    }
```

# まとめ
位置情報を扱うのも提供ライブラリがあり、すんなり実装できた
最近アサインされたPJは、ドメイン駆動設計などを取り入れていて
そのうちでかくなってきたらこっちにも取り入れたいなと思ってます。