Is it possible to add graticule tick marks and coordinates around the edges of a leaflet
plot? Something like the screenshot below but with lon/lat values included? I looked online but did not find a solution.
map <- leaflet() %>%
addTiles() %>%
setView(lng = -118.88, lat = 38.1341, zoom = 8) #|>
#addCoordinates()
map
I combined the compass-solution with the autograticule plugin.
1 - you can specify the distance of graticule lines depending on the zoom level. Just adjust the if-else setting the lineOffset
in below code.
2 - at the end of the code, I added some css to adjust the style of the labels, adjust the fontsize / color to your liking.
3 - the graticule labels are shown within the leaflet map, therefore I had to make sure, that the existing UI does not obstruct the labels. So the Latitude labels are offset from the left edge by 4 %, the Longitude 5 % from the bottom. Adjust this as needed.
library(leaflet)
library(htmltools)
map <- leaflet() %>%
addTiles() %>%
setView(lng = -118.88, lat = 38.1341, zoom = 8) %>%
addScaleBar(position = "bottomleft", options = scaleBarOptions(maxWidth = 100, metric = TRUE, imperial = FALSE)) %>%
addControl(HTML('
<div style="width: 90px;height: 90px;">
<img src="https://clipart-library.com/images/rTnRjnAGc.png" style="width: 100%; width: 100%; opacity: 0.7;">
</div>'), position = "topright", className = "leaflet-control-nobg") %>%
htmlwidgets::onRender("
function(el, x) {
// Store reference to map
var map = this;
// Define the AutoGraticule plugin directly
L.AutoGraticule = L.LayerGroup.extend({
options: {
redraw: 'move',
minDistance: 20
},
initialize: function(options) {
L.LayerGroup.prototype.initialize.call(this);
L.Util.setOptions(this, options);
this._layers = {};
this._zoom = -1;
},
onAdd: function(map) {
this._map = map;
map.on('viewreset ' + this.options.redraw, this._reset, this);
this._reset();
},
onRemove: function(map) {
map.off('viewreset ' + this.options.redraw, this._reset, this);
this.clearLayers();
},
_reset: function() {
this.clearLayers();
var currentZoom = this._map.getZoom(); // 0 completely zoomed out, 18-20 completely zoomed in
var bounds = this._map.getBounds();
var sw = bounds.getSouthWest();
var ne = bounds.getNorthEast();
// Calculate a better position for longitude labels (5% from bottom of map)
var labelLat = sw.lat + (ne.lat - sw.lat) * 0.05;
// Determine line offset of graticules based on zoom level using a formula
//var lineOffset = Math.round(40 * Math.pow(0.6, currentZoom) * 100) / 100;
// or manually
var lineOffset;
if (currentZoom <= 2) {
lineOffset = 30;
} else if (currentZoom <= 4) {
lineOffset = 10;
} else if (currentZoom <= 6) {
lineOffset = 5;
} else if (currentZoom <= 8) {
lineOffset = 2;
} else if (currentZoom <= 10) {
lineOffset = 1;
} else if (currentZoom <= 12) {
lineOffset = 0.5;
}else {
lineOffset = 0.1;
}
console.log('Current Zoom: ', currentZoom, ', LineOffset: ', lineOffset);
// longitude lines
for (var lng = Math.floor(sw.lng / lineOffset) * lineOffset;
lng <= Math.ceil(ne.lng / lineOffset) * lineOffset;
lng += lineOffset) {
var roundedLng = Math.round(lng * 100) / 100; // Round to 2 decimal places
var line = L.polyline([[sw.lat, roundedLng], [ne.lat, roundedLng]],
{color: '#888', weight: 1, opacity: 0.5});
this.addLayer(line);
if (Math.abs(roundedLng) > 0.001) { // Avoid adding label at exactly 0
// Format label based on line offset - show decimals only when needed
var lngLabel = roundedLng.toFixed(lineOffset < 1 ? 1 : 0) + '°';
var lngMarker = L.marker([labelLat, roundedLng], {
icon: L.divIcon({
className: 'leaflet-graticule-label',
html: lngLabel,
iconSize: [0, 0],
iconAnchor: [0, 0]
})
});
this.addLayer(lngMarker);
}
}
// Draw latitude lines
for (var lat = Math.floor(sw.lat / lineOffset) * lineOffset;
lat <= Math.ceil(ne.lat / lineOffset) * lineOffset;
lat += lineOffset) {
var roundedLat = Math.round(lat * 100) / 100; // Round to 2 decimal places
var line = L.polyline([[roundedLat, sw.lng], [roundedLat, ne.lng]],
{color: '#888', weight: 1, opacity: 0.5});
this.addLayer(line);
if (Math.abs(roundedLat) > 0.001) { // Avoid adding label at exactly 0
// Format label based on line offset - show decimals only when needed
var latLabel = roundedLat.toFixed(lineOffset < 1 ? 1 : 0) + '°';
// adjust left offset
var latMarker = L.marker([roundedLat, sw.lng + (ne.lng - sw.lng) * 0.04], {
icon: L.divIcon({
className: 'leaflet-graticule-label',
html: latLabel,
iconSize: [0, 0],
iconAnchor: [0, 0]
})
});
this.addLayer(latMarker);
}
}
// Add some CSS for the labels
if (!document.getElementById('graticule-style')) {
var style = document.createElement('style');
style.id = 'graticule-style';
style.innerHTML = '.leaflet-graticule-label { color: black; font-weight: bold; font-size: 14px; white-space: nowrap; text-shadow: 1px 1px 1px #fff, -1px 1px 1px #fff, 1px -1px 1px #fff, -1px -1px 1px #fff; }';
document.head.appendChild(style);
}
}
});
L.autoGraticule = function(options) {
return new L.AutoGraticule(options);
};
// Add the graticule to the map
var graticule = L.autoGraticule().addTo(map);
}
")
map <- htmlwidgets::prependContent(map,
tags$style(".leaflet-control-nobg { background: none !important; box-shadow: none !important; border: none !important; }")
)
map