はじめに (対象読者・この記事でわかること)

この記事は、JavaScriptとLeafletライブラリを使用してWeb地図アプリケーションを開発している中級者以上の開発者を対象としています。特に、Leaflet Drawプラグインを利用してユーザーにポリゴンなどの図形を描画してもらう機能を実装している開発者に向けています。

この記事を読むことで、Leaflet Drawを使用してPolygonを描画する際に発生するdraw:drawvertexイベントが2回ずつ発火する問題の原因を理解し、適切な解決策を実装できるようになります。イベントハンドラの最適化方法や、イベントの重複を防ぐための実装テクニックについて具体的なコード例と共に学べます。

前提知識

この記事を読み進める上で、以下の知識があるとスムーズです。 - HTML/CSS/JavaScriptの基本的な知識 - Leafletライブラリの基本的な使い方 - イベント処理の基本的な理解 - Web地図アプリケーションの開発経験

Leaflet Drawとdraw:drawvertexイベントの概要

Leaflet Drawは、Leafletライブラリを拡張するプラグインで、Web地図上での図形の描画、編集、削除機能を提供します。開発者はこのプラグインを利用することで、ユーザーが直接地図上にポリゴン、ライン、マーカーなどの図形を作成・編集できるインターフェースを簡単に実装できます。

draw:drawvertexイベントは、ユーザーがポリゴンやラインの頂点を追加または移動した際に発火するイベントです。このイベントは、頂点の座標情報を含むイベントオブジェクトを引数として受け取り、描画中の図形のリアルタイムな状態を監視するために利用されます。

例えば、ユーザーがポリゴンを描画している最中に頂点を追加するたびに、その頂点の座標を取得してサーバーに送信したり、UI上に何らかのフィードバックを表示したりする場合にこのイベントが使用されます。

draw:drawvertexイベントが2回発火する問題の詳細と解決策

問題の現象

Leaflet Drawを使用してポリゴンを描画する際に、draw:drawvertexイベントが意図せず2回ずつ発火することがあります。これは、ユーザーが頂点を追加または移動するたびに、同じ処理が2回実行されることを意味します。

例えば、以下のようなコードでイベントリスナーを登録した場合を考えてみましょう。

Javascript
map.on(L.Draw.Event.DRAWVERTEX, function(e) { console.log('頂点が追加/移動されました:', e.layer); // ここに頂点の座標を取得する処理などを記述 });

このコードを実行し、ユーザーがポリゴンの頂点を追加すると、コンソールには同じメッセージが2回表示されます。この問題により、不要なAPIリクエストが2回送られたり、UIが更新されすぎてパフォーマンスが低下したりする可能性があります。

問題の原因

この問題の原因は、Leaflet Drawの内部実装にあります。Leaflet Drawは、頂点の追加/移動を検知するために、内部的に複数のイベントリスナーを登録しています。その結果、ユーザーが1回の操作を行うと、複数のイベントが連鎖的に発生し、draw:drawvertexイベントが重複して発火することがあります。

具体的には、以下のようなイベントフローが考えられます。

  1. ユーザーが頂点をドラッグして移動する
  2. Leaflet Drawがmousedownイベントを検知
  3. Leaflet Drawがmousemoveイベントを検知
  4. Leaflet Drawがmouseupイベントを検知
  5. Leaflet Drawが内部的にdraw:drawvertexイベントを発火
  6. Leaflet Drawがdraw:editedイベントを発火する際に、再度draw:drawvertexイベントを発火

このような内部イベントの連鎖により、draw:drawvertexイベントが重複して発火することがあります。

解決策1: イベントフラグの使用

最も簡単な解決策は、イベントが重複して発火しないようにフラグを使用して制御する方法です。

Javascript
let isProcessing = false; map.on(L.Draw.Event.DRAWVERTEX, function(e) { if (isProcessing) return; isProcessing = true; try { console.log('頂点が追加/移動されました:', e.layer); // ここに頂点の座標を取得する処理などを記述 } finally { isProcessing = false; } });

この方法では、イベント処理中に再度同じイベントが発火しても無視するようにしています。ただし、この方法ではイベントの重複を防ぐだけで、根本的な原因を解決しているわけではないことに注意が必要です。

解決策2: イベントのデバウンス

より洗練された解決策として、イベントのデバウンス(処理の遅延実行)を使用する方法があります。デバウンスを使用することで、短時間に連続して発火するイベントを1回にまとめることができます。

Javascript
function debounce(func, wait) { let timeout; return function() { const context = this; const args = arguments; clearTimeout(timeout); timeout = setTimeout(() => { func.apply(context, args); }, wait); }; } map.on(L.Draw.Event.DRAWVERTEX, debounce(function(e) { console.log('頂点が追加/移動されました:', e.layer); // ここに頂点の座標を取得する処理などを記述 }, 100));

この方法では、イベント発火から100ms待ってから処理を実行するようにしています。この間に同じイベントが再度発火すると、タイマーがリセットされるため、最後のイベントのみが処理されます。

解決策3: Leaflet Drawのカスタマイズ

より根本的な解決策として、Leaflet Drawのソースコードをカスタマイズしてイベントの重複発火を防ぐ方法があります。ただし、この方法はLeaflet Drawのバージョンアップ時に変更が必要になるため、メンテナンスコストがかかる点に注意が必要です。

Leaflet Drawのソースコード内で、Draw.PolylineDraw.Polygonクラスの_fireCreatedEvent_fireUpdatedEventメソッドをオーバーライドして、イベントの重複を防ぐことができます。

Javascript
// Leaflet Drawのソースコードをカスタマイズ L.Draw.Polygon.include({ _fireCreatedEvent: function() { // イベントが重複して発火しないように制御 if (!this._fired) { this._fired = true; L.Draw.Feature.prototype._fireCreatedEvent.call(this); setTimeout(() => { this._fired = false; }, 100); } } });

解決策4: 別のイベントの使用

場合によっては、draw:drawvertexイベントの代わりに別のイベントを使用することで問題を回避できることがあります。例えば、draw:editedイベントを使用する方法があります。

Javascript
map.on(L.Draw.Event.EDITED, function(e) { console.log('図形が編集されました:', e.layers); // ここに編集後の図形の情報を取得する処理などを記述 });

draw:editedイベントは、図形の編集が完了した後に1回だけ発火するため、イベントの重複を気にする必要がありません。ただし、このイベントは編集完了後に発火するため、リアルタイムなフィードバックが必要な場合は不向きです。

解決策5: Leaflet Drawのバージョンアップ

Leaflet Drawのバージョンによっては、この問題が修正されている可能性があります。最新のバージョンにアップデートすることで、問題が解決されることがあります。

Bash
npm install leaflet-draw@latest

または、CDNを使用している場合は、最新のバージョンを指定して読み込みます。

Html
<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet.draw/1.0.4/leaflet.draw.js"></script>

推奨される解決策

これらの解決策の中で、特に推奨されるのは解決策2のイベントのデバウンスです。なぜなら、以下の理由からです。

  1. Leaflet Drawのソースコードを変更する必要がない
  2. バージョンアップ時に問題が起きにくい
  3. 他のイベント処理にも応用が利く
  4. パフォーマンスの向上にもつながる

ただし、アプリケーションの要件によっては、他の解決策の方が適している場合もあります。例えば、リアルタイムなフィードバックが必要な場合は解決策1のイベントフラグが、編集完了後の処理が必要な場合は解決策4の別のイベントの使用が適しています。

まとめ

本記事では、Leaflet Drawを使用してPolygonを描画する際に発生するdraw:drawvertexイベントの重複発火問題について解説しました。

  • 問題の原因: Leaflet Drawの内部実装によるイベントの連鎖
  • 解決策: イベントフラグの使用、イベントのデバウンス、Leaflet Drawのカスタマイズ、別のイベントの使用、バージョンアップなど
  • 推奨される解決策: イベントのデバウンス

この記事を通して、Leaflet Drawを使用した地図アプリケーション開発で遭遇するイベント処理の問題に対する理解が深まったことと思います。今後は、より高度な地図アプリケーションの開発に挑戦してみてください。

参考資料