Leaflet Tooltip – How to Remove Leaflet Tooltip Label Collision

leaflettooltip

I am using a permanent tooltip on each marker but the problem is that tooltip texts are overlapping each other and markers are updating every 10 seconds interval. I can't use LayerGroup collision because I need to show markers. I can't find any solution to solve this issue. I tried labelgun but no luck. I used BingMap before in my project and BingMap manages this overlapping issue internally.

Here is my sample code

   
   let marker = L.marker(pos, {options});
   marker.bindTooltip(vehicleName, {
                       direction: 'auto',
                       permanent: true,
                       className: "my-labels",
                       offset: L.point(5,15)
   });
   
   newLayer.addLayer(marker);
   newLayer.addTo(map);

enter image description here

See below image how BingMap shows label
enter image description here

Best Answer

This will be brute force simple solution for the case when tooltip number is not too high. It's based on the fact it's possible to get all HTML elements with specific CSS class name with the call document.getElementsByClassName. This can be used to get all tooltip elements, if we assign them some dummy CSS class with the className option.

Logic of the solution goes like this (unoptimized brute force):

  • get all tooltip HTML elements with the document.getElementsByClassName call;
  • get bounding rects of all the elements with the .getBoundingClientRect() method;
  • set all elements visible;
  • iterate through all the elements and hide overlapping ones;
  • this process is done after each map zoom.

Example below is based on creating 250 random polygons with permanent tooltips with the help of turf.js library (here is working JSFiddle: https://jsfiddle.net/TomazicM/L2kewosz/).

function overlap(rect1, rect2) {
  return(!(rect1.right < rect2.left || 
          rect1.left > rect2.right || 
          rect1.bottom < rect2.top || 
          rect1.top > rect2.bottom));
}

function hideOverlappingTooltips() {
  var rects = [];
  var tooltips = document.getElementsByClassName('myTooltip');
  for (var i = 0; i < tooltips.length; i++) {
    tooltips[i].style.visibility = '';
    rects[i] = tooltips[i].getBoundingClientRect();
  }
  for (var i = 0; i < tooltips.length; i++) {
    if (tooltips[i].style.visibility != 'hidden') {
      for (var j = i + 1; j < tooltips.length; j++) {
        if (overlap(rects[i], rects[j])) tooltips[j].style.visibility = 'hidden';
      }
    }
  }
}

var map = L.map('map');

L.tileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
  maxZoom: 18,
  attribution: '&copy; <a href="http://openstreetmap.org/copyright">OpenStreetMap</a> contributors'
}).addTo(map);

function onEachFeature(feature, layer) {
  layer.bindTooltip(feature.id.toString(), {
    permanent: true,
    className: 'myTooltip'
  });
}

polygons = turf.randomPolygon(250, {bbox: [13.24, 45.5, 16.35, 46.84], max_radial_length: 0.01 });
turf.featureEach(polygons, function (currentFeature, featureIndex) {
  currentFeature.id = featureIndex;
});
var geojson = L.geoJson(polygons, {
  onEachFeature: onEachFeature
}).addTo(map);

map.setView(geojson.getBounds().getCenter(), 10);

hideOverlappingTooltips();

map.on('zoomend', function(evt) {
  hideOverlappingTooltips();
});