📝

MapLibre GL JSで経路に矢印を表示する方法

2023/10/16に公開

はじめに

配達用の地図システムなどで経路を表示する場合、向きを明確にするために矢印を表示したい場合があります
例えば、大阪を適当に1周するような経路表示を想定します
経路のみ
線だけではどちらがスタート地点なのかがわかりません
スタート地点がわからない
また、拡大してしまうとどちらの向きに移動するのかがわかりません
向きがわからない
こういった問題を解消するために、経路上に矢印を表示する方法を紹介します
経路と矢印

矢印の追加方法

まずは線を表示する

線のデータをソースとして登録し、それを表示するレイヤーを追加します

const route = [
    [135.4955900233997, 34.698450748884916],
    [135.5005439639132, 34.698188783671206],
    [135.50128743433436, 34.69639866583216],
    [135.50097878125513, 34.69250430212237],
    [135.50371989069944, 34.692114414923196],
    [135.50517207344322, 34.69201907509368],
    [135.5067346883476, 34.69176937501961],
    [135.50876779691947, 34.691084504323605],
    [135.51017727306348, 34.690281926158484],
    [135.51168045399163, 34.6902112989023],
    [135.51738129391993, 34.6897788176345],
    [135.5222559135254, 34.69149447689167],
    [135.52256448265857, 34.689619683390085],
    [135.52244327398563, 34.68931806964845],
    [135.5217032631404, 34.68924463309281],
    [135.5201594473576, 34.688287329158555],
    [135.52012436063646, 34.68817192739066],
    [135.52013074004032, 34.687760151589615],
    [135.52076043953252, 34.68565653541521],
    [135.5214808717457, 34.68319659987836],
    [135.51730414967503, 34.68336802479903],
    [135.5116905444989, 34.683482875606586],
    [135.50650281412805, 34.683548993751245],
    [135.4987086550054, 34.68362987184963],
    [135.4977549053385, 34.68375483012705],
    [135.49699707082655, 34.68372144593622],
    [135.49671784813788, 34.68867668369645],
    [135.49619590243265, 34.69240192172987],
    [135.49539818925896, 34.69669024706072],
];
map.on('load', () => {
    map.addSource('route', {
        type: 'geojson',
        data: {
            type: 'Feature',
            geometry: {
                type: 'LineString',
                coordinates: route
            },
            properties: {},
        }
    });


    map.addLayer({
        id: 'route',
        type: 'line',
        source: 'route',
        paint: {
            'line-color': '#00F',
            'line-width': 8
        }
    });
});

画像を用意する

簡単な形状ですが、右向きの三角形を画像として作成します
右向き三角形

画像を読み込み、線の上に表示する

先ほど作成した矢印の画像を読み込みます
読み込みが完了したら、矢印表示用のレイヤーを追加します

map.loadImage(
    'arrow.png',
    (error, image) => {
        if (error) throw error;
        map.addImage('arrow', image);
        map.addLayer({
            id: 'directions',
            type: 'symbol',
            source: 'route',
            paint: {},
            layout: {
                'symbol-placement': 'line',
                'icon-image': 'arrow',
                'symbol-spacing': 150
            }
        })
    }
);

symbol-spacingで矢印の間隔を調整することができます(デフォルト値は250px)
今回の例ではデフォルトだと間隔が広く感じたので、少し狭くしました
参考:https://maplibre.org/maplibre-style-spec/layers/#layout-symbol-symbol-spacing

画像に関して注意事項

画像を右向きで作成しなかった場合はicon-rotateで適宜回転させてください
例えば上向きの画像を作成した場合は下記のように90度回転させる必要があります

layout: {
  'symbol-placement': 'line',
  'icon-image': 'arrow',
  'symbol-spacing': 150,
  'icon-rotate': 90
}

最後に

全体のソースは以下の通りです

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <script src="https://unpkg.com/maplibre-gl@latest/dist/maplibre-gl.js"></script>
    <link href="https://unpkg.com/maplibre-gl@latest/dist/maplibre-gl.css" rel="stylesheet" />
</head>
<body>
<div id="map" style="width: 1000px; height: 1000px"></div>
<script>
    const map = new maplibregl.Map({
        container: 'map',
        style: 'https://gsi-cyberjapan.github.io/gsivectortile-mapbox-gl-js/std.json',
        center: [135.5005439639132, 34.698188783671206],
        zoom: 17,
        minZoom: 4,
        maxZoom: 17,
    });
    const route = [
        [135.4955900233997, 34.698450748884916],
        [135.5005439639132, 34.698188783671206],
        [135.50128743433436, 34.69639866583216],
        [135.50097878125513, 34.69250430212237],
        [135.50371989069944, 34.692114414923196],
        [135.50517207344322, 34.69201907509368],
        [135.5067346883476, 34.69176937501961],
        [135.50876779691947, 34.691084504323605],
        [135.51017727306348, 34.690281926158484],
        [135.51168045399163, 34.6902112989023],
        [135.51738129391993, 34.6897788176345],
        [135.5222559135254, 34.69149447689167],
        [135.52256448265857, 34.689619683390085],
        [135.52244327398563, 34.68931806964845],
        [135.5217032631404, 34.68924463309281],
        [135.5201594473576, 34.688287329158555],
        [135.52012436063646, 34.68817192739066],
        [135.52013074004032, 34.687760151589615],
        [135.52076043953252, 34.68565653541521],
        [135.5214808717457, 34.68319659987836],
        [135.51730414967503, 34.68336802479903],
        [135.5116905444989, 34.683482875606586],
        [135.50650281412805, 34.683548993751245],
        [135.4987086550054, 34.68362987184963],
        [135.4977549053385, 34.68375483012705],
        [135.49699707082655, 34.68372144593622],
        [135.49671784813788, 34.68867668369645],
        [135.49619590243265, 34.69240192172987],
        [135.49539818925896, 34.69669024706072],
    ];

    map.on('load', () => {
        map.addSource('route', {
            type: 'geojson',
            data: {
                type: 'Feature',
                geometry: {
                    type: 'LineString',
                    coordinates: route
                },
                properties: {},
            }
        });

        map.addLayer({
            id: 'route',
            type: 'line',
            source: 'route',
            paint: {
                'line-color': '#00F',
                'line-width': 8
            }
        });

        map.loadImage(
            'arrow.png',
            (error, image) => {
                if (error) throw error;
                map.addImage('arrow', image);
                map.addLayer({
                    id: 'directions',
                    type: 'symbol',
                    source: 'route',
                    paint: {},
                    layout: {
                        'symbol-placement': 'line',
                        'icon-image': 'arrow',
                        'symbol-spacing': 150
                    }
                })
            }
        );
    });
</script>
</body>
</html>

Discussion