Laravel との連携

Google Maps API を Laravel と連携させてみましょう。

( Laravel10 基礎の環境などを利用すると、データ容量にも余裕があり良いでしょう)

モデルの作成

さまざまな場所を表す Place モデルを作成します。

php artisan make:model Place --migration

マイグレーションファイルの編集

<?php

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

return new class extends Migration
{
    /**
     * Run the migrations.
     */
    public function up(): void
    {
        Schema::create('places', function (Blueprint $table) {
            $table->id();
            $table->string('name', 200);
            $table->string('address', 200);
            $table->timestamps();
        });
    }

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

マイグレーションの実行

php artisan migrate

$fillable の設定

Place.php

<?php

namespace App\Models;


use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Place extends Model
{
    public $fillable = ['name', 'place'];

    use HasFactory;
}

Place の仮データを登録

Seeder を利用して Place の仮データを登録しておきます。

  1. PlaceSeeder の作成
  2. PlaceSeeder に追記
  3. DatabaseSeeder に PlaceSeeder を登録
  4. Seeder の実行

の手順で実行します。

1. PlaceSeeder の作成

以下のコマンドで PlaceSeeder を生成します。

php artisan make:seeder PlaceSeeder

2. PlaceSeeder に追記

PlaceSeeder に追記して、仮データを登録しておきます。

database/seeders/PlaceSeeder.php

<?php

use Illuminate\Database\Seeder;

class PlaceSeeder extends Seeder
{
    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run(): void
    {
        \App\Models\Place::create([
            'name' => '池袋',
            'address' => '東京都豊島区池袋',
        ]);

        \App\Models\Place::create([
            'name' => '六本木',
            'address' => '東京都港区六本木',
        ]);

        \App\Models\Place::create([
            'name' => '品川駅',
            'address' => '東京都港区高輪3丁目26−27',
        ]);
    }
}

DatabaseSeeder に PlaceSeeder を登録

database/seeders/DatabaseSeeder.php

<?php

use Illuminate\Database\Seeder;

class DatabaseSeeder extends Seeder
{
    /**
     * Seed the application's database.
     *
     * @return void
     */
    public function run(): void
    {
        // PlaceSeeder を登録
        $this->call([
            PlaceSeeder::class,
        ]);
    }
}

Seeder の実行

php artisan db:seed

これで、仮データの追加は完了です。

コントローラーの作成

Place モデルを利用するコントローラである PlaceController を作成します。

php artisan make:controller PlaceController --resource

ルーティング

web.php でリソースルーティングを利用してルーティングします。

use \App\Http\Controllers\PlaceController;

上記で PlaceController を use した上で

web.php

Route::resource('places', PlaceController::class);

として、PlaceController に対してリソースルーティングを行います。

PlaceController の index メソッドを記述

PlaceController の index メソッドを記述します。

まず、Place モデルを use した上で

use App\Models\Place;

index メソッドを以下のように記述します。

PlaceController.php

    public function index()
    {
        $places = Place::all();
        return view('places.index', [
            'title' => 'スポット一覧',
            'places' => $places,
        ]);
    }

API_KEY を .env の末尾に記述

APIキーの値を利用するため .env ファイルに以下の記述を行います。

API_KEY=(APIキーの値)

これにより

env('API_KEY')

の記述でAPIキーの値が利用できるようになります。

places.index のテンプレート

最後にテンプレートを用意しておきましょう。

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>おすすめプレイス一覧</title>
    <style>
        #map_box {
            width: 800px;
            height: 600px;
        }
    </style>
</head>
<body>
    <h1>おすすめプレイス一覧</h1>
    <div id="map_box"></div>
    <p id="search_result"></p>
    <h2>一覧</h2>
    @if($places->isNotEmpty())
        <table>
            <thead>
                <tr>
                    <th>ID</th>
                    <th>場所名</th>
                    <th>住所</th>
                    <th>表示</th>
                </tr>
            </thead>
            <tbody>
                @foreach($places as $place)
                    <tr>
                        <td>{{ $place->id }}</td>
                        <td>{{ $place->name }}</td>
                        <td>{{ $place->address }}</td>
                        <td>
                            <button
                                class="display"
                                data-address="{{ $place->address }}"
                                data-name="{{ $place->name }}"
                            >
                                表示
                            </button>
                        </td>
                    </tr>
                @endforeach
            </tbody>
        </table>
    @else
        <p>登録されたプレイスはありません。</p>
    @endif

    <script>
        (g=>{var h,a,k,p="The Google Maps JavaScript API",c="google",l="importLibrary",q="__ib__",m=document,b=window;b=b[c]||(b[c]={});var d=b.maps||(b.maps={}),r=new Set,e=new URLSearchParams,u=()=>h||(h=new Promise(async(f,n)=>{await (a=m.createElement("script"));e.set("libraries",[...r]+"");for(k in g)e.set(k.replace(/[A-Z]/g,t=>"_"+t[0].toLowerCase()),g[k]);e.set("callback",c+".maps."+q);a.src=`https:\/\/maps.${c}apis.com/maps/api/js?`+e;d[q]=f;a.onerror=()=>h=n(Error(p+" could not load."));a.nonce=m.querySelector("script[nonce]")?.nonce||"";m.head.append(a)}));d[l]?console.warn(p+" only loads once. Ignoring:",g):d[l]=(f,...n)=>r.add(f)&&u().then(()=>d[l](f,...n))})({
            key: "{{ env('API_KEY') }}",
        });
    </script>

    <script>
        let map;
        // async は非同期で実行される関数
        async function initMap() {
            // await は非同期処理が終わるまで待つ
            // Map は Google Maps のクラス
            const { Map } = await google.maps.importLibrary("maps");
            const { AdvancedMarkerElement, PinElement } = await google.maps.importLibrary("marker");

            let shinagawa = {
                lat: 35.6284477,
                lng: 139.7366322
            };
            let map_box = document.getElementById('map_box');
            let map = new google.maps.Map(
                map_box,
                {
                    center: shinagawa,
                    zoom: 15,
                    disableDefaultUI: true,
                    zoomControl: true,
                    clickableIcons: false,
                    mapId: 'DEMO_MAP_ID', // マップのIDを指定
                }
            );
            // ジオコーダーの生成
            let geocoder = new google.maps.Geocoder();
            let places = {{ Js::from($places) }};
            places.forEach(function(place){
                geocoder.geocode(
                    // 第一引数にジオコーディングのオプションを設定
                    {
                        address: place.address
                    },
                    // 第二引数に結果取得時の動作を設定
                    function(results, status){
                        if(status !== 'OK'){
                            alert('ジオコーディングに失敗しました。結果: ' + status);
                            return;
                        }
                        if(!results[0]){
                            alert('検索結果がありません。');
                            return;
                        }
                        let added_marker = new AdvancedMarkerElement({
                            position: results[0].geometry.location,
                            map: map,
                        });
                        let infoWindow = new google.maps.InfoWindow({
                            content: place.name, // nameに設定した値
                        });
                        // マーカークリック時に情報ウィンドウを開く
                        added_marker.addListener(
                            'click',
                            function(){
                              infoWindow.open(map, this);
                            }
                        );
                    }
                );
            });

            // ボタンを取得し配列に変換。
            let display_buttons = Array.from(document.getElementsByClassName('display'));
            //各ボタンにイベントを設定
            display_buttons.forEach(
                function(display_button){
                    display_button.addEventListener(
                    'click',
                    function(){
                        geocoder.geocode(
                            // 第一引数にジオコーディングのオプションを設定
                            {
                                address: display_button.dataset.address
                            },
                            // 第二引数に結果取得時の動作を設定
                            function(results, status){
                                // 失敗時の処理
                                if(status !== 'OK'){
                                    alert('ジオコーディングに失敗しました。結果: ' + status);
                                    return;
                                }
                                // 成功した場合、resultsの0番目に結果が取得される。
                                if(!results[0]){
                                    alert('検索結果がありません');
                                    return;
                                }
                                map.panTo(results[0].geometry.location); // スムーズに移動
                                document.getElementById('search_result').innerHTML = results[0].formatted_address;
                            }
                        );
                    }
                    );
                }
            );
        }
        initMap();
    </script>
</body>
</html>

上記のスクリプトの読み込み箇所で

    <script>
        (g=>{var h,a,k,p="The Google Maps JavaScript API",c="google",l="importLibrary",q="__ib__",m=document,b=window;b=b[c]||(b[c]={});var d=b.maps||(b.maps={}),r=new Set,e=new URLSearchParams,u=()=>h||(h=new Promise(async(f,n)=>{await (a=m.createElement("script"));e.set("libraries",[...r]+"");for(k in g)e.set(k.replace(/[A-Z]/g,t=>"_"+t[0].toLowerCase()),g[k]);e.set("callback",c+".maps."+q);a.src=`https:\/\/maps.${c}apis.com/maps/api/js?`+e;d[q]=f;a.onerror=()=>h=n(Error(p+" could not load."));a.nonce=m.querySelector("script[nonce]")?.nonce||"";m.head.append(a)}));d[l]?console.warn(p+" only loads once. Ignoring:",g):d[l]=(f,...n)=>r.add(f)&&u().then(()=>d[l](f,...n))})({
            key: "{{ env('API_KEY') }}",
        });
    </script>

として、

env('API_KEY')

が活用されています。

また、

let places = {{ Js::from($places) }};

とすることで Js ファサードの from メソッドを用いて、PHPの変数である $places を json 形式に変換して JavaScript 内で利用しています。

results matching ""

    No results matching ""