Laravel + Leaflet 地図で駅名検索できるようにする

こんにちは❗フリーランス・エンジニアの 九保すこひ です。

さてさて、この間「夜景マップを作ってみる」でleaflet.jsを使ってみましたが、これすごく便利ですよね。

もちろん機能ではかなわないですが Googleマップは有料化してしまったので「ちょっとした地図」が必要な場合は、十分すぎる機能です。

ただ、前回使ってみたときに一点ある不満がありました。

それが・・・・・・

検索ができない

ことです。

例えば、「大阪駅周辺の地図がみたい」と思っても、今いる場所が大阪じゃない場合GPSデータを使えるわけではないので、

1.地図を掴んで、2.滑らせる

を延々とやらないといけなくなります。(ズームを変えるにしてもこの作業、面倒ですよね)

そこで❗

今回は、この欠点をleafletのプラグイン「leaflet-search」を使って実装してみることにします。

つくりたいのは、「駅名で検索して、自動でその位置へ移動する」機能です。
ぜひ何かの参考になりましたら嬉しいです。😄✨

「GPSをリアルタイムで取得できれば、
Pusherとかと連携して、
『見守り機能』もつくれそうですね👍」

開発環境: Laravel 8.x、leaflet 1.7、leaflet-search 3

処理の流れ

駅名検索ができるようにするための処理は以下のとおりです。

  1. 地図上にある検索ボックスに駅名を入力
  2. Ajax でその駅名を送信
  3. 送信先で該当するデータを検索する
  4. 検索ボックスの下に候補を表示
  5. 選択するとその駅の位置へ移動

では実際に作業をしていきましょう❗

駅の位置データを用意する

では、まずはAjaxを通して取得する駅データを用意しましょう。

なんとありがたいことに、駅データ.jp というサイトさんが無料&商用OKなデータを公開してくれています。

ただし、ダウンロードには会員登録が必要なので以下のURLから登録すませておいてください。

📝 新規登録ページ駅データ.jp:新規登録

会員登録が完了したら、ダウンロードページ からログインします。
すると、ページ左側のメニューがあるので、その中の「データダウンロード」をクリック。

ページが移動するので、「駅データ」の最新データをダウンロードしてください。形式はCSVです。

そして、ファイルがダウンロードできたらstorage/app/csvフォルダに移動しておいてください。

これでデータのダウンロードは完了です。

駅データをデータベースへ移す

さすがにCSVのままでは検索などに利用しにくいので、内容を全てDBへ移動します。

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

php artisan make:model Station -msc

すると、「モデル」だけでなく「マイグレーション」と「コントローラー」ファイルが作成されるので中身を変更していきます。

マイグレーションの設定

まずはマイグレーションです。

database/migrations/****_**_**_******_create_stations_table.php

// 省略

public function up()
{
    Schema::create('stations', function (Blueprint $table) {
        $table->id();
        $table->string('name')->comment('駅名');
        $table->double('latitude', 9, 6)->comment('緯度');
        $table->double('longitude', 9, 6)->comment('経度');
        $table->timestamps();
    });
}

※ 今回は必要最低限のカラムだけ用意していますが、お好みで追加してください。

Seederの設定

続いてSeederでDB再構築する際に、先ほどのCSVからデータが移行できるようにします。

database/seeders/StationSeeder.php

<?php

namespace Database\Seeders;

use App\Models\Station;
use Illuminate\Database\Seeder;

class StationSeeder extends Seeder
{
    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run()
    {
        $csv_path = storage_path('app/csv/station20210312free.csv'); // ダウンロードしたファイルへのパス
        $file = new \SplFileObject($csv_path);
        $file->setFlags(\SplFileObject::READ_CSV);

        foreach ($file as $index => $row) {

            if($index > 0 && !is_null($row[0])) {

                $station_name = $row[2];
                $longitude = floatval($row[9]);
                $latitude = floatval($row[10]);

                $station = new Station();
                $station->name = $station_name;
                $station->longitude = $longitude;
                $station->latitude = $latitude;
                $station->save();

            }

        }
    }
}

次に、Seederはつくっただけでは有効にならないので、Laravel側へ登録します。

database/seeders/DatabaseSeeder.php

public function run()
{
    // 省略

    $this->call(StationSeeder::class);
}

では、以下のコマンドでデータベースを再構築してみましょう。

⚠ご注意: なお、10,000件以上の駅データがあるので少し時間がかかるかもしれません。

php artisan migrate:fresh --seed

再構築が完了すると、テーブルは以下のようになりました。

コントローラーの設定

そして、コントローラーに、「マップの表示」と「Ajaxで駅名検索する」機能をつくります。

app/Http/Controllers/StationController.php

<?php

namespace App\Http\Controllers;

use App\Models\Station;
use Illuminate\Http\Request;

class StationController extends Controller
{
    public function index() {

        return view('station.index');

    }

    public function list(Request $request)
    {
        if($request->filled('keyword')) {

            return Station::where('name', 'LIKE', '%'. $request->keyword .'%')
                ->take(5)
                ->get()
                ->map(function($station){ // leaflet-search 用にデータを加工する

                    return [
                        'loc' => [
                            $station->latitude,
                            $station->longitude
                        ],
                        'title' => $station->name
                    ];

                });

        }

        return [];
    }
}

この中では、検索キーワードがあるときは駅名で検索し、さらに取得できたデータをこの後で登場するプラグイン「leaflet-search」に合わせてデータを加工しています。

ルートをつくる

続いては、ルートです。

use App\Http\Controllers\StationController;

// 省略

Route::get('station', [StationController::class, 'index'])->name('station.index');
Route::get('station/list', [StationController::class, 'list'])->name('station.list');

ビューをつくる

最後に、先ほどのコントローラーで指定したビューをつくります。

resources/views/station/index.blade.php

<html>
<head>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.1/dist/css/bootstrap.min.css" rel="stylesheet">
    <link rel="stylesheet" href="https://unpkg.com/leaflet@1.7.1/dist/leaflet.css">
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/leaflet-search@3.0.2/dist/leaflet-search.min.css">
</head>
<body>
<div id="app" class="p-4">
    <h1>Leaflet に駅名検索をつくるサンプル</h1>
    <div id="my-map" style="height:500px;"></div>
</div>
<script src="https://unpkg.com/leaflet@1.7.1/dist/leaflet.js"></script>
<script src="https://cdn.jsdelivr.net/npm/leaflet-search@3.0.2/dist/leaflet-search.min.js"></script>
<script>

    window.addEventListener('load', () => {

        const tileLayerUrl = 'https://cyberjapandata.gsi.go.jp/xyz/pale/{z}/{x}/{y}.png';
        const attribution = '<a href="https://www.gsi.go.jp/kikakuchousei/kikakuchousei40182.html" target="_blank" rel="noopener">国土地理院</a>';

        const map = L.map('my-map').setView([35.71006814475588, 139.81069905886525], 15); // スタートは東京スカイツリー
        L.tileLayer(tileLayerUrl, { attribution: attribution }).addTo(map);
        L.control.search({
            url: '{{ route('station.list') }}?keyword={s}',
            position: 'topright',
            textErr: '候補が見つかりませんでした',
            textPlaceholder: '駅名で検索...',
            textCancel: 'キャンセル'
        }).addTo(map);

    });

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

この中で重要なのは、プラグイン「leaflet-search」を使っている部分です。

パラメータのurlに先ほどコントローラーで作ったlist()へのURLをセットしていますが、ここの{s}の部分が検索ボックスに入力されたテキストと置き換わります。

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

URL例:

http://******/station/list?keyword=(URLエンコードされたテキスト)

これで作業を終了です。
お疲れ様でした✨😄👍

ちなみに: Vue 3との連携は・・・

私はビルドなしで使えるVueが好きなのですが、Vue 3 + leafletを使うとズーム時にエラーが発生する可能性があります。

というのも、Vue 3はdata()内に入った値をProxy化するので、どうやらそれが影響するようです。

そのため、もし使うなら「Vue外にある変数mapleafletmapオブジェクトをセットして使うようにするといいようです。(ただし、コンポーネントなど多重で呼び出す場合はうまくいきませんので、注意が必要です)

※ なお、markRawを使って「そのまま」指定をしてもエラーが発生してしまいました。Vue 2なら問題なく動くのですが・・・やはり「シンプルなのに高機能」ですし、Vue 2の方が使い勝手いいかもですね。😂

デモを用意しました

では実際のテストですが、触ってもらった方が早いと思うのでデモページを用意しました。

※ ただし、あまりにも全駅データでは多すぎるので、以下の駅だけしか検索できませんのでご注意ください。

  • 東京
  • 渋谷
  • 原宿
  • 新宿
  • 新橋

ぜひ試してみてください。

📝 デモページ: こちら

企業様へのご提案

現在、Googleマップの利用は有料となっています。

そのため、「Googleマップほど高機能でなくてもいい」ということでしたら、今回の「検索機能付きのマップ」でランニング・コストをカットすることができます。(完全無料で使えます)

また、今回は駅データを使いましたが、緯度経度データさえあれば、支社、支店、営業所名、または配送先やお客様の自宅の検索も可能です。

ぜひこういった機能をご希望でしたら、ぜひお問い合わせよりご連絡ください。
お待ちしております。m(_ _)m

※ なお、今回の駅データも含めまして緯度・経度データのダウンロード・ページをまとめました。ぜひこちらもご参考になってください。

おわりに

ということで今回はLaravel + Leaflet+leaflet-searchで「駅名検索できるマップ」をつくってみました。

個人的には、駅データだけでなくいろんな「珍スポット」and「B級スポット」の緯度経度データがほしいところなので、また調査してみようと考えています。

ぜひ皆さんも面白そうな使い方を考えてみてくださいね。

ではでは〜❗

「よし、次はここへ行こう❗」

開発のご依頼お待ちしております 😊✨ どうぞよろしくお願いいたします!
このエントリーをはてなブックマークに追加       follow us in feedly