Mapbox GL JS で進行方向の矢印付き linestring

こんにちは。
Mapbox GL JS で linestring を描画し、進行方向を示す矢印（矢尻形）を付けました[^1]。
* ただし見栄えが良くないです（ズーム倍率条件によっては顕著[^2]）。

[^1]: 参考：「[Mapbox GL JS で矢尻形マーカー（mapboxgl.Marker 利用）](https://qiita.com/kkdd/items/0eb24549d10e875c1fa5)」
[^2]: 参考："[Showing Direction Arrow on Line in Mapboxgl](https://stackoverflow.com/questions/46102553/showing-direction-arrow-on-line-in-mapboxgl)" (stack overflow)

<img width="500" alt="mapbox.jpg" src="https://qiita-image-store.s3.amazonaws.com/0/54617/0ec858a0-514f-5653-8658-431d251faa35.jpeg">

```mapboxgl_linestring_with_arrowhead.html
<!DOCTYPE html>
<html>
<head>
    <meta charset='utf-8' />
    <title></title>
    <meta name='viewport' content='initial-scale=1,maximum-scale=1,user-scalable=no' />
    <script src='https://api.tiles.mapbox.com/mapbox-gl-js/v1.12.0/mapbox-gl.js'></script>
    <link href='https://api.tiles.mapbox.com/mapbox-gl-js/v1.12.0/mapbox-gl.css' rel='stylesheet' />
    <style>
        body { margin:0; padding:0; }
        #map { position:absolute; top:0; bottom:0; width:100%; }
    </style>
</head>
<body>
<div id='map'></div>
<script>
//mapboxgl.accessToken = '<your access token here>';
const map = new mapboxgl.Map({
    container: 'map',
    style: 'mapbox://styles/mapbox/bright-v9',
    center: [-122.488, 37.831],
    zoom: 15
});

map.addControl(new mapboxgl.NavigationControl());
map.addControl(new mapboxgl.ScaleControl({maxWidth: 80}));

map.on('load', async () => {
    map.addSource("route", {
        "type": "geojson",
        "data": {
            "type": "Feature",
            "properties": {},
            "geometry": {
                "type": "LineString",
                "coordinates": [
                    [-122.48369693756104, 37.83381888486939],
                    [-122.48348236083984, 37.83317489144141],
                    [-122.48339653015138, 37.83270036637107],
                    [-122.48356819152832, 37.83205636317963],
                    [-122.48404026031496, 37.83114119107971],
                    [-122.48404026031496, 37.83049717427869],
                    [-122.48348236083984, 37.82992094395505],
                    [-122.48356819152832, 37.82954808664175],
                    [-122.48507022857666, 37.82944639795659],
                    [-122.48610019683838, 37.82880236636284],
                    [-122.48695850372314, 37.82931081282506],
                    [-122.48700141906738, 37.83080223556934],
                    [-122.48751640319824, 37.83168351665737],
                    [-122.48803138732912, 37.83215804826779],
                    [-122.48888969421387, 37.83297152392784],
                    [-122.48987674713133, 37.83263257682617],
                    [-122.49043464660643, 37.83293762928776],
                    [-122.49125003814696, 37.83242920781773],
                    [-122.49163627624512, 37.83256478721899],
                    [-122.49223709106445, 37.83337825839438],
                    [-122.49378204345702, 37.83368330777276]
                ]
            }
        }
    });

    map.addLayer({
        "id": "route",
        "type": "line",
        "source": "route",
        "paint": {
            "line-color": "#a4a",
            "line-width": 3
        }
    });

	// arrowhead
	map.addImage('arrow-head', await arrowHeadImage(14, "#0a0"));

	map.addLayer({
	    "id": "arrowhead",
	    "type": "symbol",
	    "source": "route",
	    "layout": {
	      "symbol-placement": "line",
	      "symbol-spacing": 100,
	      "icon-image": "arrow-head",
	      "icon-offset": [8, 0],
	    }
	});
});

function arrowHeadImage(size, color) {
	const param = {"color": color, "size": size, "rotation": 90};
	const data = `<svg width='${param.size}' height='${param.size}' viewBox='0 0 100 100' xmlns='http://www.w3.org/2000/svg' version='1.1'><polygon fill='${param.color}' stroke='gray' stroke-width='1' points='20,90 50,10 80,90 50,70' transform='rotate(${param.rotation} 50 50)'/></svg>`;
	return new Promise((resolve) => {
		const img = new Image(param.size, param.size);
		img.src = "data:image/svg+xml;base64," + btoa(data);
		img.onload = () => resolve(createImageBitmap(img));
	});
}
</script>
</body>
</html>
```