【Laravel】QRコードで駐車位置・表示システムをつくる

こんにちは。フリーランス・コンサルタント&エンジニアの 九保すこひ です。

さてさて、この間ある記事を読んで「面白そう!」という思う内容がありました。

それは・・・・・・

QRコードで駐車エリアを保存し、後でそれがどこだったかが分かる

というシステムでした。

つまり、大きなショッピングセンターにお買い物にいって「あれ、ウチの車どこに停めたっけ!?」がなくなるシステムということですね。

そして、この記事を読んでみて「へぇ、QRコードのこんな使い方があるんだ!」と感心すると同時に、どうしても自分で作ってみたくなりました。

そこで❗

今回はLaravelを使ってこの「駐車位置・保存システム」を実装してみたいと思います。

ぜひ学習のお役にたてますと嬉しいです。😊✨
(最後に、今回実際に開発したソースコード一式をダウンロードできますよ👍)

「ペーパー・ドライバーです 📝」

開発環境: Laravel 8.x、Vue 3

やりたいこと

今回実装するのは、以下の流れです。

  1. 駐車したエリアにあるQRコードを読み取る(= エリアデータをブラウザに保存)
  2. (お買い物に行く)
  3. 出口に設置されたQRコードを読み取ると、保存エリアにマークがついた地図が表示される

なお、今回テストで使う地図は以下になります。

※ 今回はテストなのでたった4エリアですが、実際はもっとたくさんあることを想定しています。

では、楽しくやっていきましょう❗

パッケージをインストールする

先にPHPQRコードを作成することができるように「php-qrcode」というパッケージをインストールしておきます。

以下のコマンドを実行してください。

composer require chillerlan/php-qrcode

そして、画像を操作できる「intervention」です。
同じくインストールしてください。

composer require intervention/image

ルートをつくる

では、続いてルートをつくります。

routes/web.php

<?php

use Illuminate\Support\Facades\Route;
use \App\Http\Controllers\ParkingController; // 👈 ここも忘れず!

// 省略

Route::prefix('parking')->group(function(){

    Route::get('/qr_code/{parking?}', [ParkingController::class, 'qr_code'])->name('parking.qr_code');
    Route::get('/show', [ParkingController::class, 'show'])->name('parking.show');

});

1つ目のルートがQRコードを表示するページです。

なお、{parking?}とハテナマークがついているのは、「パラメータがなくてもOK 👍」にするためです。

つまり、以下のようなURLがOKになります。

  • http://******/parking/qrcode/1 :数字はIDで可変
  • http://******/parking/qrcode : こっちもOK

そして、2つ目ですが、これは「駐車位置のパラメータがある or ない」でそれぞれ以下のように動作が変わるようにしています。

  • パラメータがある: そのパラメータをブラウザへ保存
  • パラメータがない: すでに保存されたデータから地図を表示

モデル&マイグレーションをつくる

続いて、データを管理するモデルとマイグレーション(DBテーブル)をつくります。

以下のコマンドを実行してください。

php artisan make:model Parking -m

すると、モデルとマイグレーションのファイルが作成されるので、マイグレーションを以下のように変更してください。

database/migrations/****_**_**_******_create_parkings_table.php

// 省略

public function up()
{
    Schema::create('parkings', function (Blueprint $table) {
        $table->id();
        $table->string('name')->comment('エリア名');
        $table->integer('location_x')->comment('エリア位置:X軸');
        $table->integer('location_y')->comment('エリア位置:Y軸');
        $table->timestamps();
    });
}

// 省略

なお、この中のlocation_xlocation_yは、最初に見てもらった地図画像上で「そのエリアがどこにあるの?」が分かる座標になります。

単位はピクセルです。

駐車エリアの情報を登録する

そして、DBテーブル「parkings」に登録するデータをつくります。
以下のコマンドでSeederファイルを作成してください。

php artisan make:seed ParkingsTableSeeder

すると、ファイルが作成されるので、中身を以下のように変更します。

database/seeders/ParkingsTableSeeder.php

// 省略

public function run()
{
    $areas = [
        [
            'name' => 'ライオンエリア',
            'location_x' => 160,
            'location_y' => 265,
        ],
        [
            'name' => '猫エリア',
            'location_x' => 480,
            'location_y' => 265,
        ],
        [
            'name' => 'うさぎエリア',
            'location_x' => 160,
            'location_y' => 410,
        ],
        [
            'name' => 'パンダエリア',
            'location_x' => 480,
            'location_y' => 410,
        ],
    ];

    foreach ($areas as $area) {

        $parking = new Parking();
        $parking->name = $area['name'];
        $parking->location_x = $area['location_x'];
        $parking->location_y = $area['location_y'];
        $parking->save();

    }
}

// 省略

続いて、このファイルが有効になるようにLaravelに登録します。

database/seeders/DatabaseSeeder.php

<?php

namespace Database\Seeders;

use Illuminate\Database\Seeder;

class DatabaseSeeder extends Seeder
{
    /**
     * Seed the application's database.
     *
     * @return void
     */
    public function run()
    {
//        User::factory(10)->create();
        $this->call(ParkingsTableSeeder::class); // 👈 ここを追加しました
    }
}

では、以下のコマンドを実行してDBテーブルを構築しましょう。

php artisan migrate:fresh --seed

実行するとテーブルはこうなります。

コントローラーをつくる

次に、コントローラーをつくります。
以下のコマンドを実行してください。

php artisan make:controller ParkingController

すると、ファイルが作成されるので中身を以下のように変更します。

app/Http/Controllers/ParkingController.php

<?php

namespace App\Http\Controllers;

use App\Models\Parking;
use chillerlan\QRCode\QRCode;
use Illuminate\Http\Request;

class ParkingController extends Controller
{
    public function qr_code(Parking $parking) {

        $url = route('parking.show', [
            'x' => $parking->location_x,
            'y' => $parking->location_y
        ]);
        $data = (new QRCode())->render($url);
        return \Image::make($data)->response();

    }

    public function show(Request $request) {

        return view('parking.show')->with([
            'x' => intval($request->x),
            'y' => intval($request->y)
        ]);

    }
}

この中でやっているのは以下のとおりです。

qr_code()

このメソッドでQRコードを作成します。

やっている事は、該当するID番号の「駐車エリア・データ」を取得し、その位置情報をxyとして「/parking/show」のURLに含め、それをQRコード化しています。

つまり、以下のようなURLになります。

http://******/parking/show?x=160&y=410

show()

ここでは、2つのコンテンツを切り替えることになります。
その詳細は次のとおりです。

  • 位置情報のパラメータがある場合: そのデータをlocalStorageへ保存
  • パラメータがない場合: 保存されている駐車エリアを表示

つまり、上の項目が駐車した直後に読み取るQRコードで、下が駐車位置を探すときのQRコードになります。

ビューをつくる

では、最後にビューをつくります。
以下のファイルを作成してください。

resources/views/parking/show.blade.php

<html>
<head>
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css">
</head>
<body>
<div id="app">
    <!-- 駐車したエリアのデータを保存する場合 -->
    <div v-if="mode=='save'">
        <div class="p-3 text-center" v-if="locationSaved">
            駐車位置を保存しました。
        </div>
    </div>
    <!-- 駐車したエリアのデータを表示する場合 -->
    <div id="canvas-box" v-else-if="mode=='show'">
        <canvas id="canvas"></canvas>
    </div>
</div>
<script src="https://unpkg.com/vue@3.0.2/dist/vue.global.prod.js"></script>
<script>

    Vue.createApp({
        data() {
            return {
                location: {
                    x: {{ $x }},
                    y: {{ $y }},
                },
                locationSaved: false,
                canvas: null,
                context: null,
                mapImage: null
            }
        },
        methods: {
            save() {

                try {

                    const parkingData = JSON.stringify(this.location);
                    localStorage.setItem('parking_location', parkingData);
                    this.locationSaved = true;

                } catch (e) {

                    console.log(e);
                    alert('残念ながらブラウザが対応していません。');

                }

            },
            show() {

                this.context.drawImage(this.mapImage, 0, 0, this.canvas.width, this.canvas.height);

                const parkingData = JSON.parse(
                    localStorage.getItem('parking_location')
                );
                const parkingX = parkingData.x;
                const parkingY = parkingData.y;
                const radius = this.canvas.width * 0.085;
                const x = this.canvas.width * parkingX / this.mapImage.width;
                const y = this.canvas.height * parkingY / this.mapImage.height;
                this.context.arc(x, y, radius, 0, 2 * Math.PI);
                this.context.strokeStyle = '#ffff99';
                this.context.lineWidth = 10;
                this.context.stroke();

            }
        },
        computed: {
            mode() {

                return (this.location.x > 0 && this.location.y > 0)
                    ? 'save'
                    : 'show';

            }
        },
        mounted() {

            if(this.mode === 'save') {

                this.save();

            } else if(this.mode === 'show') {

                this.mapImage = new Image;
                this.mapImage.onload = () => {

                    const canvasWidth = document.querySelector('#canvas-box').clientWidth;
                    const canvasHeight = canvasWidth * this.mapImage.height / this.mapImage.width;
                    this.canvas = document.querySelector('#canvas');
                    this.canvas.width = canvasWidth;
                    this.canvas.height = canvasHeight;
                    this.context = this.canvas.getContext('2d');
                    this.show();

                };
                this.mapImage.src = '/images/parking_map.png';

            }

        }
    }).mount('#app');

</script>
</body>
</html>

このビューは、パラメータの状態によって以下2つのmodeになります。

  • save: 駐車した場所を「保存」する
  • show: 駐車した場所を「表示」する

ではひとつずつご紹介します。

saveモードの場合

ページが表示されるとすぐにsave()メソッドが実行されます。

このメソッドの中では、ブラウザのlocalStorageへデータを保存することになりますが、念のためlocalStorageが使えないブラウザのことも考え、try ~ catchで例外処理をつけています。

show モードの場合

このモードの場合、いろいろとやっているのですが、目的は「横幅いっぱいに地図を表示する」です。

つまり、今回の地図画像は640 x 480 ピクセルですが、表示する環境はそれぞれ違ってきますので、100%の横幅にします。

ただ、そうなってくると、location_xlocation_yとして取得した値が「拡大 or 縮小」している地図に対して、正しい位置ではなくなってしまいます。

そのため、位置を「割合」にすることでこれを解決しています。(こういう計算って難しいですよね・・・💧)

テストしてみる

では、実際にテストしてみましょう❗
なお、地図はこうなっています。

駐車位置を保存するテスト

まずは「ライオンエリア」に車を停めることを想定してやってみます。
ブラウザで「http://******/parking/qr_code/1」にアクセスしてください。

QRコードが表示されるのでこれを読み取り、ブラウザで表示します。

すると、駐車位置がブラウザに保存されます。

では、本当にlocalStorageに保存されてるか確認してみましょう。

まずは1段階目は成功ですね👍

駐車位置を表示するテスト

そして、次は「お買い物後」を想定してテストします。

今回はパラメータをつけずにQRコード・ページへアクセスします。URLは、「http://******/parking/qr_code」です。

すると、またQRコードが表示されるので、再度読み取ってブラウザで表示します。

すると・・・・・・

はい❗
うまく「ライオンエリア」に○マークがつきました。

成功です😊✨

では、念のために同じ手順で「うさぎエリア」だった場合でもテストしてみます。

  1. 「http://******/parking/qr_code/3」へアクセス
  2. QRコード読み取り(駐車位置を保存)
  3. 「http://******/parking/qr_code」へアクセス
  4. QRコード読み取り
  5. 確認

すると・・・・・・

はい❗
今度は「うさぎエリア」に○マークがつきました。

こちらも成功です😊✨

ダウンロードする

以下から今回実際に開発したソースコード一式をダウンロードすることができます。

【Laravel】QRコードで駐車位置・表示システムをつくる

※ ただし、マイグレーションなどはご自身で実行してください。

※ また、地図に含まれているイラストはフリーライセンスで公開していないので、この中には含まれていません。テストする場合は、このページからダウンロードして使ってください。

開発のご依頼お待ちしております
開発のご依頼はこちらから: お問い合わせ
どうぞよろしくお願いいたします! by 九保すこひ

おわりに

ということで、今回はQRコードを使った「駐車位置システム」を作ってみました。

ちなみに、今回は駐車位置を表示するだけでしたが、当初は「現在地からどう行けばそのエリアに到着できるか?」という2地点を想定していました。

しかし、あまりにも複雑になりそうで断念することになりました(また、2階・3階などの情報も扱ってみたかったんですが・・・それもやっちゃうと学習には不向きになるという結論になり、これもやめました😅)

とはいえ、基本のコードを使っていろいろ拡張ができると思いますので、ぜひ試して見てくださいね。

ではでは〜❗

「えっ、カビキラーって、
こんな落ちるんですね😳」

このエントリーをはてなブックマークに追加       follow us in feedly