Leafletは地図を表示するためのJavaScriptライブラリです。このライブラリを使って地図上にバツ印を描く例を紹介します。
実際のデモとコードが次です。
/**
* バツ印を描画。バツ印はstartからendまでの線分とその線分の中点を通る直交した同じ長さの線分で描画されます
* @param map {L.Map} 地図
* @param start {L.LatLng} バツ印の片方の線の開始地点
* @param end {L.LatLng} バツ印の片方の線の終了地点
* @param options {PolylineOptions} バツ印をのオプション (色や太さなど)
* @returns {L.LayerGroup} バツ印を描画したレイヤーグループ
*/
const drawCross = (map, start, end, options) => {
// 画面上でバツ印にするためには地球の形状が計算の邪魔になるので、計算は全てピクセル座標系で行います
// 地理座標からピクセル座標への変換
const startPoint = map.latLngToContainerPoint(start);
const endPoint = map.latLngToContainerPoint(end);
// 中点を計算
const midpoint = L.point((startPoint.x + endPoint.x) / 2, (startPoint.y + endPoint.y) / 2);
// ドラッグ開始点から終了点までの垂直方向の差分の計算
const dx = endPoint.x - startPoint.x;
const dy = endPoint.y - startPoint.y;
// 中点からドラッグ開始点から終了点までの線分に垂直かつ半分の長さの線分を計算
// dx, dyを入れ替えてそれぞれの方向に伸ばすことで元の線分を90度回転させる
const lineBOnContainerPoint = [
L.point(midpoint.x - dy / 2, midpoint.y + dx / 2),
L.point(midpoint.x + dy / 2, midpoint.y - dx / 2),
];
// ピクセル座標から地理座標へ変換
const lineB = [
map.containerPointToLatLng(lineBOnContainerPoint[0]),
map.containerPointToLatLng(lineBOnContainerPoint[1]),
];
// バツ印を描画
return L.layerGroup([L.polyline([start, end], options), L.polyline(lineB, options)]).addTo(map);
};
/**
* マウスダウン時のハンドラを作成。このマウスダウンを起点にバツ印を描画する
* @param map {L.Map} 地図
* @param crossOptions {PolylineOptions} バツ印のオプション (色や太さなど)
* @param onCreated {Function} バツ印が地図上に作成された際に呼び出される関数
*/
const makeMouseDownHandler = (map, crossOptions, onCreated) => (e) => {
// ドラッグ開始地点を記録
const startPoint = e.latlng;
// ドラッグ中は地図の移動を無効化
// マウス移動時にバツ印を描画するので、地図の移動を無効化しないと描画ができない
map.dragging.disable();
// マウス移動時にバツ印し続けるために描画したバツ印を記録して消してを繰り返す
let drawingCross = null;
function onMouseMove(e) {
if (drawingCross) {
map.removeLayer(drawingCross);
}
drawingCross = drawCross(map, startPoint, e.latlng, crossOptions);
}
map.on('mousemove', onMouseMove);
// ドラッグ終了地点でバツ印を描画し、地図の移動を有効化
map.once('mouseup', (e) => {
map.dragging.enable();
map.off('mousemove', onMouseMove);
const cross = drawCross(map, startPoint, e.latlng, crossOptions);
onCreated && onCreated(cross);
});
};
/**
* バツ印描画ハンドラを追加
* @param map {L.Map} 地図
* @param options {PolylineOptions} バツ印のオプション (色や太さなど)
* @param onCreated {Function} バツ印が地図上に作成された際に呼び出される関数
* @returns {Function} バツ印描画ハンドラを削除する関数
*/
const addDrawCrossHandler = (map, options, onCreated) => {
const mouseDownHandler = makeMouseDownHandler(map, options, onCreated);
map.on('mousedown', mouseDownHandler);
return () => map.off('mousedown', mouseDownHandler);
};
/*****************************
* 使用例
*****************************/
// 地図を作成
const map = L.map('map').setView([34.727682,137.715498], 17);
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: 'Map data © <a href="https://www.openstreetmap.org/copyright">OpenStreetMap contributors</a>',
}).addTo(map);
// バツ印描画ハンドラを追加
addDrawCrossHandler(
map,
{color: 'red', weight: 8},
(cross) => {
console.log('バツ印が作成されました')
console.log(cross);
}
);
やっていることはコメントの通りです。地図上でマウスダウンした地点からマウスアップした地点までの線分と、その線分の中点を通る垂直で同じ長さの線分を描画することでバツ印を地図上に描画しています。こういった見た目の形が定まった図形を作図する場合は、地球の形状を考慮せずにピクセル座標系で計算するという手法が有効です。緯度経度のままで計算する場合は地球が楕円体であることやメルカトル図法の歪みなどの影響により一筋縄ではいかなくなります。
バツ印の形状以外は何がしかの作図で共通して作ると便利な機能です。makeMouseDownHandler内のマウス移動時に描画した図形を消して再描画するという手法はリアルタイムに図形を見ることができ目当ての図形を作りやすくなります。引数でリレーしているonCreatedは図形が作成された際に呼び出されるイベントハンドラー的な関数であり、地図上に描画された後の処理を書きやすくできます。