Use native decluttering in vector-label-decluttering example
This commit is contained in:
@@ -4,10 +4,8 @@ title: Vector Label Decluttering
|
|||||||
shortdesc: Label decluttering with a custom renderer.
|
shortdesc: Label decluttering with a custom renderer.
|
||||||
resources:
|
resources:
|
||||||
- https://cdn.polyfill.io/v2/polyfill.min.js?features=Set"
|
- 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: >
|
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.
|
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, renderer, labelgun, label"
|
tags: "vector, decluttering, labels"
|
||||||
---
|
---
|
||||||
<div id="map" class="map"></div>
|
<div id="map" class="map"></div>
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
// NOCOMPILE
|
|
||||||
/* global labelgun */
|
|
||||||
goog.require('ol.Map');
|
goog.require('ol.Map');
|
||||||
goog.require('ol.View');
|
goog.require('ol.View');
|
||||||
goog.require('ol.extent');
|
goog.require('ol.extent');
|
||||||
@@ -9,26 +7,7 @@ goog.require('ol.source.Vector');
|
|||||||
goog.require('ol.style.Fill');
|
goog.require('ol.style.Fill');
|
||||||
goog.require('ol.style.Stroke');
|
goog.require('ol.style.Stroke');
|
||||||
goog.require('ol.style.Style');
|
goog.require('ol.style.Style');
|
||||||
|
goog.require('ol.style.Text');
|
||||||
// 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 = {};
|
|
||||||
|
|
||||||
var map = new ol.Map({
|
var map = new ol.Map({
|
||||||
target: '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({
|
var labelStyle = new ol.style.Style({
|
||||||
renderer: function(coords, state) {
|
geometry: function(feature) {
|
||||||
var text = state.feature.get('name');
|
var geometry = feature.getGeometry();
|
||||||
createLabel(textCache[text], text, coords);
|
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({
|
var countryStyle = new ol.style.Style({
|
||||||
fill: new ol.style.Fill({
|
fill: new ol.style.Fill({
|
||||||
@@ -72,54 +56,18 @@ var countryStyle = new ol.style.Style({
|
|||||||
width: 1
|
width: 1
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
var styleWithLabel = [countryStyle, labelStyle];
|
var style = [countryStyle, labelStyle];
|
||||||
var styleWithoutLabel = [countryStyle];
|
|
||||||
|
|
||||||
var pixelRatio; // This is set by the map's precompose listener
|
|
||||||
var vectorLayer = new ol.layer.Vector({
|
var vectorLayer = new ol.layer.Vector({
|
||||||
source: new ol.source.Vector({
|
source: new ol.source.Vector({
|
||||||
url: 'data/geojson/countries.geojson',
|
url: 'data/geojson/countries.geojson',
|
||||||
format: new ol.format.GeoJSON()
|
format: new ol.format.GeoJSON()
|
||||||
}),
|
}),
|
||||||
style: function(feature, resolution) {
|
style: function(feature) {
|
||||||
var text = feature.get('name');
|
labelStyle.getText().setText(feature.get('name'));
|
||||||
var width = textMeasureContext.measureText(text).width;
|
return style;
|
||||||
var geometry = feature.getGeometry();
|
},
|
||||||
if (geometry.getType() == 'MultiPolygon') {
|
declutter: true
|
||||||
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);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
map.addLayer(vectorLayer);
|
map.addLayer(vectorLayer);
|
||||||
|
|||||||
Reference in New Issue
Block a user