
九保すこひです(フリーランスのITコンサルタント、エンジニア)
さてさて、この間「夜景マップを作ってみる」でleaflet.js
を使ってみましたが、これすごく便利ですよね。
もちろん機能ではかなわないですが Googleマップは有料化してしまったので「ちょっとした地図」が必要な場合は、十分すぎる機能です。
ただ、前回使ってみたときに一点ある不満がありました。
それが・・・・・・
検索ができない
ことです。
例えば、「大阪駅周辺の地図がみたい」と思っても、今いる場所が大阪じゃない場合GPSデータを使えるわけではないので、
1.地図を掴んで、2.滑らせる
を延々とやらないといけなくなります。(ズームを変えるにしてもこの作業、面倒ですよね)
そこで
今回は、この欠点をleaflet
のプラグイン「leaflet-search」を使って実装してみることにします。
つくりたいのは、「駅名で検索して、自動でその位置へ移動する」機能です。
ぜひ何かの参考になりましたら嬉しいです。
「GPSをリアルタイムで取得できれば、
Pusherとかと連携して、
『見守り機能』もつくれそうですね」
開発環境: Laravel 8.x、leaflet 1.7、leaflet-search 3
目次 [非表示]
処理の流れ
駅名検索ができるようにするための処理は以下のとおりです。
- 地図上にある検索ボックスに駅名を入力
- Ajax でその駅名を送信
- 送信先で該当するデータを検索する
- 検索ボックスの下に候補を表示
- 選択するとその駅の位置へ移動
では実際に作業をしていきましょう
駅の位置データを用意する
では、まずは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外にある変数」map
にleaflet
のmap
オブジェクトをセットして使うようにするといいようです。(ただし、コンポーネントなど多重で呼び出す場合はうまくいきませんので、注意が必要です)
※ なお、markRaw
を使って「そのまま」指定をしてもエラーが発生してしまいました。Vue 2
なら問題なく動くのですが・・・やはり「シンプルなのに高機能」ですし、Vue 2
の方が使い勝手いいかもですね。
デモを用意しました
では実際のテストですが、触ってもらった方が早いと思うのでデモページを用意しました。
※ ただし、あまりにも全駅データでは多すぎるので、以下の駅だけしか検索できませんのでご注意ください。
- 東京
- 渋谷
- 原宿
- 新宿
- 新橋
ぜひ試してみてください。
デモページ: こちら
企業様へのご提案
現在、Googleマップの利用は有料となっています。
そのため、「Googleマップほど高機能でなくてもいい」ということでしたら、今回の「検索機能付きのマップ」でランニング・コストをカットすることができます。(完全無料で使えます)
また、今回は駅データを使いましたが、緯度経度データさえあれば、支社、支店、営業所名、または配送先やお客様の自宅の検索も可能です。
ぜひこういった機能をご希望でしたら、ぜひお問い合わせよりご連絡ください。
お待ちしております。m(_ _)m
※ なお、今回の駅データも含めまして緯度・経度データのダウンロード・ページをまとめました。ぜひこちらもご参考になってください。
おわりに
ということで今回はLaravel
+ Leaflet
+leaflet-search
で「駅名検索できるマップ」をつくってみました。
個人的には、駅データだけでなくいろんな「珍スポット」and「B級スポット」の緯度経度データがほしいところなので、また調査してみようと考えています。
ぜひ皆さんも面白そうな使い方を考えてみてくださいね。
ではでは〜
「よし、次はここへ行こう
」