Merge pull request #7292 from ahocevar/polygon-labels
Only render polygon labels when they fit
This commit is contained in:
@@ -2,6 +2,10 @@
|
||||
|
||||
### Next Release
|
||||
|
||||
#### Behavior change for polygon labels
|
||||
|
||||
Polygon labels are now only rendered when the label does not exceed the polygon at the label position. To get the old behavior, configure your `ol.style.Text` with `exceedLength: true`.
|
||||
|
||||
#### Minor change for custom `tileLoadFunction` with `ol.source.VectorTile`
|
||||
|
||||
It is no longer necessary to set the projection on the tile. Instead, the `readFeatures` method must be called with the tile's extent as `extent` option and the view's projection as `featureProjection`.
|
||||
|
||||
@@ -3,8 +3,8 @@ layout: example.html
|
||||
title: Vector Layer
|
||||
shortdesc: Example of a countries vector layer with country information.
|
||||
docs: >
|
||||
The countries are loaded from a GeoJSON file. Information about countries is shown on hover and click. Zoom in a few times to see country name labels.
|
||||
tags: "vector, osm, xml, loading, server"
|
||||
The countries are loaded from a GeoJSON file. Information about countries is shown on hover and click.
|
||||
tags: "vector, geojson"
|
||||
---
|
||||
<div id="map" class="map"></div>
|
||||
<div id="info"> </div>
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
goog.require('ol.Map');
|
||||
goog.require('ol.View');
|
||||
goog.require('ol.format.GeoJSON');
|
||||
goog.require('ol.layer.Tile');
|
||||
goog.require('ol.layer.Vector');
|
||||
goog.require('ol.source.OSM');
|
||||
goog.require('ol.source.Vector');
|
||||
goog.require('ol.style.Fill');
|
||||
goog.require('ol.style.Stroke');
|
||||
@@ -36,19 +34,14 @@ var vectorLayer = new ol.layer.Vector({
|
||||
url: 'data/geojson/countries.geojson',
|
||||
format: new ol.format.GeoJSON()
|
||||
}),
|
||||
style: function(feature, resolution) {
|
||||
style.getText().setText(resolution < 5000 ? feature.get('name') : '');
|
||||
style: function(feature) {
|
||||
style.getText().setText(feature.get('name'));
|
||||
return style;
|
||||
}
|
||||
});
|
||||
|
||||
var map = new ol.Map({
|
||||
layers: [
|
||||
new ol.layer.Tile({
|
||||
source: new ol.source.OSM()
|
||||
}),
|
||||
vectorLayer
|
||||
],
|
||||
layers: [vectorLayer],
|
||||
target: 'map',
|
||||
view: new ol.View({
|
||||
center: [0, 0],
|
||||
@@ -56,36 +49,32 @@ var map = new ol.Map({
|
||||
})
|
||||
});
|
||||
|
||||
var highlightStyleCache = {};
|
||||
var highlightStyle = new ol.style.Style({
|
||||
stroke: new ol.style.Stroke({
|
||||
color: '#f00',
|
||||
width: 1
|
||||
}),
|
||||
fill: new ol.style.Fill({
|
||||
color: 'rgba(255,0,0,0.1)'
|
||||
}),
|
||||
text: new ol.style.Text({
|
||||
font: '12px Calibri,sans-serif',
|
||||
fill: new ol.style.Fill({
|
||||
color: '#000'
|
||||
}),
|
||||
stroke: new ol.style.Stroke({
|
||||
color: '#f00',
|
||||
width: 3
|
||||
})
|
||||
})
|
||||
});
|
||||
|
||||
var featureOverlay = new ol.layer.Vector({
|
||||
source: new ol.source.Vector(),
|
||||
map: map,
|
||||
style: function(feature, resolution) {
|
||||
var text = resolution < 5000 ? feature.get('name') : '';
|
||||
if (!highlightStyleCache[text]) {
|
||||
highlightStyleCache[text] = new ol.style.Style({
|
||||
stroke: new ol.style.Stroke({
|
||||
color: '#f00',
|
||||
width: 1
|
||||
}),
|
||||
fill: new ol.style.Fill({
|
||||
color: 'rgba(255,0,0,0.1)'
|
||||
}),
|
||||
text: new ol.style.Text({
|
||||
font: '12px Calibri,sans-serif',
|
||||
text: text,
|
||||
fill: new ol.style.Fill({
|
||||
color: '#000'
|
||||
}),
|
||||
stroke: new ol.style.Stroke({
|
||||
color: '#f00',
|
||||
width: 3
|
||||
})
|
||||
})
|
||||
});
|
||||
}
|
||||
return highlightStyleCache[text];
|
||||
style: function(feature) {
|
||||
highlightStyle.getText().setText(feature.get('name'));
|
||||
return highlightStyle;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -7676,8 +7676,9 @@ olx.style.TextOptions;
|
||||
|
||||
|
||||
/**
|
||||
* When `placement` is set to `'line'`, allow text to exceed the length of the
|
||||
* path that it follows. Default is `false`.
|
||||
* For polygon labels or when `placement` is set to `'line'`, allow text to
|
||||
* exceed the width of the polygon at the the label position or the length of
|
||||
* the path that it follows. Default is `false`.
|
||||
* @type {boolean|undefined}
|
||||
* @api
|
||||
*/
|
||||
|
||||
@@ -14,7 +14,8 @@ goog.require('ol.geom.flat.contains');
|
||||
* @param {Array.<number>} flatCenters Flat centers.
|
||||
* @param {number} flatCentersOffset Flat center offset.
|
||||
* @param {Array.<number>=} opt_dest Destination.
|
||||
* @return {Array.<number>} Destination.
|
||||
* @return {Array.<number>} Destination point as XYM coordinate, where M is the
|
||||
* length of the horizontal intersection that the point belongs to.
|
||||
*/
|
||||
ol.geom.flat.interiorpoint.linearRings = function(flatCoordinates, offset,
|
||||
ends, stride, flatCenters, flatCentersOffset, opt_dest) {
|
||||
@@ -61,10 +62,10 @@ ol.geom.flat.interiorpoint.linearRings = function(flatCoordinates, offset,
|
||||
pointX = flatCenters[flatCentersOffset];
|
||||
}
|
||||
if (opt_dest) {
|
||||
opt_dest.push(pointX, y);
|
||||
opt_dest.push(pointX, y, maxSegmentLength);
|
||||
return opt_dest;
|
||||
} else {
|
||||
return [pointX, y];
|
||||
return [pointX, y, maxSegmentLength];
|
||||
}
|
||||
};
|
||||
|
||||
@@ -75,7 +76,8 @@ ol.geom.flat.interiorpoint.linearRings = function(flatCoordinates, offset,
|
||||
* @param {Array.<Array.<number>>} endss Endss.
|
||||
* @param {number} stride Stride.
|
||||
* @param {Array.<number>} flatCenters Flat centers.
|
||||
* @return {Array.<number>} Interior points.
|
||||
* @return {Array.<number>} Interior points as XYM coordinates, where M is the
|
||||
* length of the horizontal intersection that the point belongs to.
|
||||
*/
|
||||
ol.geom.flat.interiorpoint.linearRingss = function(flatCoordinates, offset, endss, stride, flatCenters) {
|
||||
var interiorPoints = [];
|
||||
|
||||
@@ -223,12 +223,13 @@ ol.geom.MultiPolygon.prototype.getFlatInteriorPoints = function() {
|
||||
|
||||
/**
|
||||
* Return the interior points as {@link ol.geom.MultiPoint multipoint}.
|
||||
* @return {ol.geom.MultiPoint} Interior points.
|
||||
* @return {ol.geom.MultiPoint} Interior points as XYM coordinates, where M is
|
||||
* the length of the horizontal intersection that the point belongs to.
|
||||
* @api
|
||||
*/
|
||||
ol.geom.MultiPolygon.prototype.getInteriorPoints = function() {
|
||||
var interiorPoints = new ol.geom.MultiPoint(null);
|
||||
interiorPoints.setFlatCoordinates(ol.geom.GeometryLayout.XY,
|
||||
interiorPoints.setFlatCoordinates(ol.geom.GeometryLayout.XYM,
|
||||
this.getFlatInteriorPoints().slice());
|
||||
return interiorPoints;
|
||||
};
|
||||
|
||||
@@ -210,11 +210,12 @@ ol.geom.Polygon.prototype.getFlatInteriorPoint = function() {
|
||||
|
||||
/**
|
||||
* Return an interior point of the polygon.
|
||||
* @return {ol.geom.Point} Interior point.
|
||||
* @return {ol.geom.Point} Interior point as XYM coordinate, where M is the
|
||||
* length of the horizontal intersection that the point belongs to.
|
||||
* @api
|
||||
*/
|
||||
ol.geom.Polygon.prototype.getInteriorPoint = function() {
|
||||
return new ol.geom.Point(this.getFlatInteriorPoint());
|
||||
return new ol.geom.Point(this.getFlatInteriorPoint(), ol.geom.GeometryLayout.XYM);
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -197,13 +197,13 @@ ol.render.canvas.TextReplay.prototype.drawText = function(geometry, feature) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.beginGeometry(geometry, feature);
|
||||
var begin = this.coordinates.length;
|
||||
|
||||
var geometryType = geometry.getType();
|
||||
var flatCoordinates = null;
|
||||
var end = 2;
|
||||
var stride = 2;
|
||||
var i, ii;
|
||||
|
||||
if (this.textState_.placement === ol.style.TextPlacement.LINE) {
|
||||
var ends;
|
||||
@@ -218,10 +218,11 @@ ol.render.canvas.TextReplay.prototype.drawText = function(geometry, feature) {
|
||||
} else if (geometryType == ol.geom.GeometryType.MULTI_POLYGON) {
|
||||
var endss = geometry.getEndss();
|
||||
ends = [];
|
||||
for (var i = 0, ii = endss.length; i < ii; ++i) {
|
||||
for (i = 0, ii = endss.length; i < ii; ++i) {
|
||||
ends.push(endss[i][0]);
|
||||
}
|
||||
}
|
||||
this.beginGeometry(geometry, feature);
|
||||
var textAlign = textState.textAlign;
|
||||
var flatOffset = 0;
|
||||
var flatEnd;
|
||||
@@ -239,8 +240,11 @@ ol.render.canvas.TextReplay.prototype.drawText = function(geometry, feature) {
|
||||
this.drawChars_(begin, end);
|
||||
begin = end;
|
||||
}
|
||||
this.endGeometry(geometry, feature);
|
||||
|
||||
} else {
|
||||
var label = this.getImage_(this.text_, !!this.textFillState_, !!this.textStrokeState_);
|
||||
var width = label.width / this.pixelRatio;
|
||||
switch (geometryType) {
|
||||
case ol.geom.GeometryType.POINT:
|
||||
case ol.geom.GeometryType.MULTI_POINT:
|
||||
@@ -259,18 +263,31 @@ ol.render.canvas.TextReplay.prototype.drawText = function(geometry, feature) {
|
||||
break;
|
||||
case ol.geom.GeometryType.POLYGON:
|
||||
flatCoordinates = /** @type {ol.geom.Polygon} */ (geometry).getFlatInteriorPoint();
|
||||
if (!textState.exceedLength && flatCoordinates[2] / this.resolution < width) {
|
||||
return;
|
||||
}
|
||||
stride = 3;
|
||||
break;
|
||||
case ol.geom.GeometryType.MULTI_POLYGON:
|
||||
flatCoordinates = /** @type {ol.geom.MultiPolygon} */ (geometry).getFlatInteriorPoints();
|
||||
var interiorPoints = /** @type {ol.geom.MultiPolygon} */ (geometry).getFlatInteriorPoints();
|
||||
flatCoordinates = [];
|
||||
for (i = 0, ii = interiorPoints.length; i < ii; i += 3) {
|
||||
if (textState.exceedLength || interiorPoints[i + 2] / this.resolution >= width) {
|
||||
flatCoordinates.push(interiorPoints[i], interiorPoints[i + 1]);
|
||||
}
|
||||
}
|
||||
end = flatCoordinates.length;
|
||||
if (end == 0) {
|
||||
return;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
}
|
||||
end = this.appendFlatCoordinates(flatCoordinates, 0, end, stride, false, false);
|
||||
this.drawTextImage_(begin, end);
|
||||
this.beginGeometry(geometry, feature);
|
||||
this.drawTextImage_(label, begin, end);
|
||||
this.endGeometry(geometry, feature);
|
||||
}
|
||||
|
||||
this.endGeometry(geometry, feature);
|
||||
};
|
||||
|
||||
|
||||
@@ -343,10 +360,11 @@ ol.render.canvas.TextReplay.prototype.getImage_ = function(text, fill, stroke) {
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {HTMLCanvasElement} label Label.
|
||||
* @param {number} begin Begin.
|
||||
* @param {number} end End.
|
||||
*/
|
||||
ol.render.canvas.TextReplay.prototype.drawTextImage_ = function(begin, end) {
|
||||
ol.render.canvas.TextReplay.prototype.drawTextImage_ = function(label, begin, end) {
|
||||
var textState = this.textState_;
|
||||
var strokeState = this.textStrokeState_;
|
||||
var pixelRatio = this.pixelRatio;
|
||||
@@ -354,8 +372,6 @@ ol.render.canvas.TextReplay.prototype.drawTextImage_ = function(begin, end) {
|
||||
var baseline = ol.render.replay.TEXT_ALIGN[textState.textBaseline];
|
||||
var strokeWidth = strokeState && strokeState.lineWidth ? strokeState.lineWidth : 0;
|
||||
|
||||
var label = this.getImage_(this.text_, !!this.textFillState_, !!this.textStrokeState_);
|
||||
|
||||
var anchorX = align * label.width / pixelRatio + 2 * (0.5 - align) * strokeWidth;
|
||||
var anchorY = baseline * label.height / pixelRatio + 2 * (0.5 - baseline) * strokeWidth;
|
||||
this.instructions.push([ol.render.canvas.Instruction.DRAW_IMAGE, begin, end,
|
||||
|
||||
@@ -195,4 +195,18 @@ describe('ol.geom.MultiPolygon', function() {
|
||||
|
||||
});
|
||||
|
||||
describe('#getInteriorPoints', function() {
|
||||
|
||||
it('returns XYM multipoint with intersection width as M', function() {
|
||||
var geom = new ol.geom.MultiPolygon([
|
||||
[[[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]]],
|
||||
[[[1, 1], [1, 2], [2, 2], [2, 1], [1, 1]]]
|
||||
]);
|
||||
var interiorPoints = geom.getInteriorPoints();
|
||||
expect(interiorPoints.getType()).to.be('MultiPoint');
|
||||
expect(interiorPoints.layout).to.be('XYM');
|
||||
expect(interiorPoints.getCoordinates()).to.eql([[0.5, 0.5, 1], [1.5, 1.5, 1]]);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -541,6 +541,17 @@ describe('ol.geom.Polygon', function() {
|
||||
|
||||
});
|
||||
|
||||
describe('#getInteriorPoint', function() {
|
||||
|
||||
it('returns XYM point with intersection width as M', function() {
|
||||
var geom = new ol.geom.Polygon([[[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]]]);
|
||||
var interiorPoint = geom.getInteriorPoint();
|
||||
expect(interiorPoint.getType()).to.be('Point');
|
||||
expect(interiorPoint.layout).to.be('XYM');
|
||||
expect(interiorPoint.getCoordinates()).to.eql([0.5, 0.5, 1]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('ol.geom.Polygon.fromExtent', function() {
|
||||
it('creates the correct polygon', function() {
|
||||
var extent = [1, 2, 3, 5];
|
||||
|
||||
48
test/spec/ol/render/canvas/textreplay.test.js
Normal file
48
test/spec/ol/render/canvas/textreplay.test.js
Normal file
@@ -0,0 +1,48 @@
|
||||
goog.require('ol.Feature');
|
||||
goog.require('ol.geom.MultiPolygon');
|
||||
goog.require('ol.geom.Polygon');
|
||||
goog.require('ol.render.canvas.TextReplay');
|
||||
goog.require('ol.style.Text');
|
||||
|
||||
describe('ol.render.canvas.TextReplay', function() {
|
||||
|
||||
it('renders polygon labels only when they fit', function() {
|
||||
var replay = new ol.render.canvas.TextReplay(1, [-180, -90, 180, 90], 0.02, 1, true);
|
||||
var geometry = new ol.geom.Polygon([[[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]]]);
|
||||
var feature = new ol.Feature(geometry);
|
||||
|
||||
replay.setTextStyle(new ol.style.Text({
|
||||
text: 'This is a long text'
|
||||
}));
|
||||
replay.drawText(geometry, feature);
|
||||
expect(replay.instructions.length).to.be(0);
|
||||
|
||||
replay.setTextStyle(new ol.style.Text({
|
||||
text: 'short'
|
||||
}));
|
||||
replay.drawText(geometry, feature);
|
||||
expect(replay.instructions.length).to.be(3);
|
||||
});
|
||||
|
||||
it('renders multipolygon labels only when they fit', function() {
|
||||
var replay = new ol.render.canvas.TextReplay(1, [-180, -90, 180, 90], 0.02, 1, true);
|
||||
var geometry = new ol.geom.MultiPolygon([
|
||||
[[[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]]],
|
||||
[[[1, 1], [1, 2], [2, 2], [2, 1], [1, 1]]]
|
||||
]);
|
||||
var feature = new ol.Feature(geometry);
|
||||
|
||||
replay.setTextStyle(new ol.style.Text({
|
||||
text: 'This is a long text'
|
||||
}));
|
||||
replay.drawText(geometry, feature);
|
||||
expect(replay.instructions.length).to.be(0);
|
||||
|
||||
replay.setTextStyle(new ol.style.Text({
|
||||
text: 'short'
|
||||
}));
|
||||
replay.drawText(geometry, feature);
|
||||
expect(replay.instructions.length).to.be(3);
|
||||
});
|
||||
|
||||
});
|
||||
Reference in New Issue
Block a user