I am currently working on a trail mapping Next.js react app where I fetch data from OpenStreetMap and illustrate trails on a map using leaflet polyline. Some trails have the tag oneway= 'yes' which I would like to represent visually using arrows on the polyline itself, that way it would be easy to tell which direction each trail went.
I have searched and tried many different avenues to try and replicate this feature in my app, using leaflet plugins such as leaflet-polylineDecorator or leaflet-arrowheads. I even tried rendering a bunch of markers and rotating them to point towards the next node, but I didn't manage to make it work and it seemed too complicated a solution for something so simple.
I am looking at implementing a simple and lightweight solution to avoid slowing the app down. Is there an easy solution which I am missing?
You can relatively easy adding decoration with leaflet.polylineDecorator
.
You'll need to create a decorator for each feature that is oneway = 'yes'
. I recommend to add the decorators to a separate L.FeatureGroup
(instead to the map directly) so you can easily remove, show or hide them depending on the zoom level.
Here is an example (using vanilla JavaScript and a test GeoJSON with polylines and a property oneway
; it will require some adaptation to your dataset and for Next.js):
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>StackOverflow Question 79028922 - Trail Directions</title>
<link rel="stylesheet" href="leaflet.css" />
<script src="leaflet.js"></script>
<script src="leaflet.polylineDecorator.js"></script>
<style>
body {
margin: 0;
}
#map {
height: 100vh;
}
</style>
</head>
<body>
<div id="map"></div>
<script>
const coordinate = [46.948056, 7.4475];
const map = L.map('map').setView(coordinate, 15);
L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
maxZoom: 22,
// add link to attribution, omitted in this code example
// due to width limit, i.e., avoid horizontal scrolling
attribution: '© OpenStreetMap',
}).addTo(map);
fetch('trails.geojson')
.then(response => response.json())
.then(trails => {
const trailLayer = L.geoJSON(trails, {
style: (feature) => {
const isOneWay = feature.properties['oneway'] == 'yes';
return {
color: isOneWay ? '#a14242' : '#160042',
};
},
}).addTo(map);
// create decorators
const decorators = trailLayer.getLayers()
.filter(layer => layer.feature.properties['oneway'] == 'yes')
.map(layer => {
// swap lat/lon (needed as GeoJSON uses a different order)
const coordinates = layer.feature.geometry.coordinates
.map(c => [c[1], c[0]]);
// create the decorator for the trail
return L.polylineDecorator(L.polyline(coordinates), {
patterns: [{
offset: '5%',
repeat: '50px',
symbol: L.Symbol.arrowHead({
pixelSize: 10,
polygon: false,
pathOptions: {
stroke: true,
color: '#a14242'
}
})
}]
});
});
const featureGroup = L.featureGroup(decorators, {}).addTo(map);
map.on('zoom', event => {
featureGroup.clearLayers();
if (map.getZoom() >= 15) {
decorators.forEach(decorator => {
featureGroup.addLayer(decorator);
});
}
});
});
</script>
</body>
</html>
The file trails.geojson
can be found here.
I also uploaded the code to a GitHub repository here and a demo can be found here.
I think using a library such as leaflet.polylineDecorator
or leaflet-arrowheads
is probably the most lightweight solution as you would need to manipulate the SVG paths manually otherwise.
Addendum: In leaflet.polylineDecorator
's README.md
there is a section recommending two light-weight alternatives for simpler cases. Leaflet.TextPath
could be worth to be investigated.