From dfa3cbf4e263c9532c46e59749039822ba1bfded Mon Sep 17 00:00:00 2001 From: Andreas Hocevar Date: Sun, 22 Oct 2017 15:51:22 +0200 Subject: [PATCH] Use native decluttering in vector-label-decluttering example --- examples/vector-label-decluttering.html | 6 +- examples/vector-label-decluttering.js | 122 +++++++----------------- 2 files changed, 37 insertions(+), 91 deletions(-) diff --git a/examples/vector-label-decluttering.html b/examples/vector-label-decluttering.html index cc45ca1d74..f070b32af6 100644 --- a/examples/vector-label-decluttering.html +++ b/examples/vector-label-decluttering.html @@ -4,10 +4,8 @@ title: Vector Label Decluttering shortdesc: Label decluttering with a custom renderer. resources: - https://cdn.polyfill.io/v2/polyfill.min.js?features=Set" - - https://unpkg.com/rbush@2.0.1/rbush.min.js - - https://unpkg.com/labelgun@0.1.1/lib/labelgun.min.js docs: > - A custom `renderer` function is used instead of an `ol.style.Text` to label the countries of the world. Only texts that are not wider than their country's bounding box are considered and handed over to [Labelgun](https://github.com/Geovation/labelgun) for decluttering. -tags: "vector, renderer, labelgun, label" + Decluttering is used to avoid overlapping labels with `exceedLength: true` set on the text style. For MultiPolygon geometries, only the widest polygon is selected in a custom `geometry` function. +tags: "vector, decluttering, labels" ---
diff --git a/examples/vector-label-decluttering.js b/examples/vector-label-decluttering.js index d3c366449a..6f58b4862e 100644 --- a/examples/vector-label-decluttering.js +++ b/examples/vector-label-decluttering.js @@ -1,5 +1,3 @@ -// NOCOMPILE -/* global labelgun */ goog.require('ol.Map'); goog.require('ol.View'); goog.require('ol.extent'); @@ -9,26 +7,7 @@ goog.require('ol.source.Vector'); goog.require('ol.style.Fill'); goog.require('ol.style.Stroke'); goog.require('ol.style.Style'); - -// Style for labels -function setStyle(context) { - context.font = '12px Calibri,sans-serif'; - context.fillStyle = '#000'; - context.strokeStyle = '#fff'; - context.lineWidth = 3; - context.textBaseline = 'hanging'; - context.textAlign = 'start'; -} - -// A separate canvas context for measuring label width and height. -var textMeasureContext = document.createElement('CANVAS').getContext('2d'); -setStyle(textMeasureContext); - -// The label height is approximated by the width of the text 'WI'. -var height = textMeasureContext.measureText('WI').width; - -// A cache for reusing label images once they have been created. -var textCache = {}; +goog.require('ol.style.Text'); var map = new ol.Map({ target: 'map', @@ -38,30 +17,35 @@ var map = new ol.Map({ }) }); -var emptyFn = function() {}; -var labelEngine = new labelgun['default'](emptyFn, emptyFn); - -function createLabel(canvas, text, coord) { - var halfWidth = canvas.width / 2; - var halfHeight = canvas.height / 2; - var bounds = { - bottomLeft: [Math.round(coord[0] - halfWidth), Math.round(coord[1] - halfHeight)], - topRight: [Math.round(coord[0] + halfWidth), Math.round(coord[1] + halfHeight)] - }; - labelEngine.ingestLabel(bounds, coord.toString(), 1, canvas, text, false); -} - -// For multi-polygons, we only label the widest polygon. This is done by sorting -// by extent width in descending order, and take the first from the array. -function sortByWidth(a, b) { - return ol.extent.getWidth(b.getExtent()) - ol.extent.getWidth(a.getExtent()); -} - var labelStyle = new ol.style.Style({ - renderer: function(coords, state) { - var text = state.feature.get('name'); - createLabel(textCache[text], text, coords); - } + 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 = ol.extent.getWidth(polygon.getExtent()); + if (width > widest) { + widest = width; + geometry = polygon; + } + } + } + return geometry; + }, + text: new ol.style.Text({ + font: '12px Calibri,sans-serif', + exceedLength: true, + fill: new ol.style.Fill({ + color: '#000' + }), + stroke: new ol.style.Stroke({ + color: '#fff', + width: 3 + }) + }) }); var countryStyle = new ol.style.Style({ fill: new ol.style.Fill({ @@ -72,54 +56,18 @@ var countryStyle = new ol.style.Style({ width: 1 }) }); -var styleWithLabel = [countryStyle, labelStyle]; -var styleWithoutLabel = [countryStyle]; +var style = [countryStyle, labelStyle]; -var pixelRatio; // This is set by the map's precompose listener var vectorLayer = new ol.layer.Vector({ source: new ol.source.Vector({ url: 'data/geojson/countries.geojson', format: new ol.format.GeoJSON() }), - style: function(feature, resolution) { - var text = feature.get('name'); - var width = textMeasureContext.measureText(text).width; - var geometry = feature.getGeometry(); - if (geometry.getType() == 'MultiPolygon') { - geometry = geometry.getPolygons().sort(sortByWidth)[0]; - } - var extentWidth = ol.extent.getWidth(geometry.getExtent()); - if (extentWidth / resolution > width) { - // Only consider label when it fits its geometry's extent - if (!(text in textCache)) { - // Draw the label to its own canvas and cache it. - var canvas = textCache[text] = document.createElement('CANVAS'); - canvas.width = width * pixelRatio; - canvas.height = height * pixelRatio; - var context = canvas.getContext('2d'); - context.scale(pixelRatio, pixelRatio); - setStyle(context); - context.strokeText(text, 0, 0); - context.fillText(text, 0, 0); - } - labelStyle.setGeometry(geometry.getInteriorPoint()); - return styleWithLabel; - } else { - return styleWithoutLabel; - } - } -}); -vectorLayer.on('precompose', function(e) { - pixelRatio = e.frameState.pixelRatio; - labelEngine.destroy(); -}); -vectorLayer.on('postcompose', function(e) { - var labels = labelEngine.getShown(); - for (var i = 0, ii = labels.length; i < ii; ++i) { - var label = labels[i]; - // Draw label to the map canvas - e.context.drawImage(label.labelObject, label.minX, label.minY); - } + style: function(feature) { + labelStyle.getText().setText(feature.get('name')); + return style; + }, + declutter: true }); map.addLayer(vectorLayer);