Vector Label Decluttering

Label decluttering with a custom renderer.

Decluttering is used to avoid overlapping labels with overflow: true set on the text style. For MultiPolygon geometries, only the widest polygon is selected in a custom geometry function.

<!DOCTYPE html>
<html>
  <head>
    <title>Vector Label Decluttering</title>
    <link rel="stylesheet" href="https://openlayers.org/en/v5.3.0/css/ol.css" type="text/css">
    <!-- The line below is only needed for old environments like Internet Explorer and Android 4.x -->
    <script src="https://cdn.polyfill.io/v2/polyfill.min.js?features=requestAnimationFrame,Element.prototype.classList,URL"></script>
    <script src="https://cdn.polyfill.io/v2/polyfill.min.js?features=Set""></script>
  </head>
  <body>
    <div id="map" class="map"></div>
    <script>
      import Map from 'ol/Map.js';
      import View from 'ol/View.js';
      import {getWidth} from 'ol/extent.js';
      import GeoJSON from 'ol/format/GeoJSON.js';
      import VectorLayer from 'ol/layer/Vector.js';
      import VectorSource from 'ol/source/Vector.js';
      import {Fill, Stroke, Style, Text} from 'ol/style.js';

      var map = new Map({
        target: 'map',
        view: new View({
          center: [0, 0],
          zoom: 1
        })
      });

      var labelStyle = new Style({
        geometry: function(feature) {
          var geometry = feature.getGeometry();
          if (geometry.getType() == 'MultiPolygon') {
            // Only render label for the widest polygon of a multipolygon
            var polygons = geometry.getPolygons();
            var widest = 0;
            for (var i = 0, ii = polygons.length; i < ii; ++i) {
              var polygon = polygons[i];
              var width = getWidth(polygon.getExtent());
              if (width > widest) {
                widest = width;
                geometry = polygon;
              }
            }
          }
          return geometry;
        },
        text: new Text({
          font: '12px Calibri,sans-serif',
          overflow: true,
          fill: new Fill({
            color: '#000'
          }),
          stroke: new Stroke({
            color: '#fff',
            width: 3
          })
        })
      });
      var countryStyle = new Style({
        fill: new Fill({
          color: 'rgba(255, 255, 255, 0.6)'
        }),
        stroke: new Stroke({
          color: '#319FD3',
          width: 1
        })
      });
      var style = [countryStyle, labelStyle];

      var vectorLayer = new VectorLayer({
        source: new VectorSource({
          url: 'data/geojson/countries.geojson',
          format: new GeoJSON()
        }),
        style: function(feature) {
          labelStyle.getText().setText(feature.get('name'));
          return style;
        },
        declutter: true
      });

      map.addLayer(vectorLayer);
    </script>
  </body>
</html>