diff --git a/changelog/upgrade-notes.md b/changelog/upgrade-notes.md
index 08853ed983..30c10d2b75 100644
--- a/changelog/upgrade-notes.md
+++ b/changelog/upgrade-notes.md
@@ -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`.
diff --git a/examples/vector-layer.html b/examples/vector-layer.html
index b44c4ed8bb..c3a457b62e 100644
--- a/examples/vector-layer.html
+++ b/examples/vector-layer.html
@@ -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"
---
diff --git a/examples/vector-layer.js b/examples/vector-layer.js
index 26fde998e6..4495d7d889 100644
--- a/examples/vector-layer.js
+++ b/examples/vector-layer.js
@@ -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;
}
});
diff --git a/externs/olx.js b/externs/olx.js
index 7cc408b62d..8b3cc7a22c 100644
--- a/externs/olx.js
+++ b/externs/olx.js
@@ -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
*/
diff --git a/src/ol/geom/flat/interiorpoint.js b/src/ol/geom/flat/interiorpoint.js
index 3311a6cf5a..f3b263964a 100644
--- a/src/ol/geom/flat/interiorpoint.js
+++ b/src/ol/geom/flat/interiorpoint.js
@@ -14,7 +14,8 @@ goog.require('ol.geom.flat.contains');
* @param {Array.} flatCenters Flat centers.
* @param {number} flatCentersOffset Flat center offset.
* @param {Array.=} opt_dest Destination.
- * @return {Array.} Destination.
+ * @return {Array.} 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.>} endss Endss.
* @param {number} stride Stride.
* @param {Array.} flatCenters Flat centers.
- * @return {Array.} Interior points.
+ * @return {Array.} 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 = [];
diff --git a/src/ol/geom/multipolygon.js b/src/ol/geom/multipolygon.js
index 7f2f9c1589..93e3a9b635 100644
--- a/src/ol/geom/multipolygon.js
+++ b/src/ol/geom/multipolygon.js
@@ -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;
};
diff --git a/src/ol/geom/polygon.js b/src/ol/geom/polygon.js
index 7884ca53fd..76d2795ab7 100644
--- a/src/ol/geom/polygon.js
+++ b/src/ol/geom/polygon.js
@@ -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);
};
diff --git a/src/ol/render/canvas/textreplay.js b/src/ol/render/canvas/textreplay.js
index a8a13274b9..d39abc05bf 100644
--- a/src/ol/render/canvas/textreplay.js
+++ b/src/ol/render/canvas/textreplay.js
@@ -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,
diff --git a/test/spec/ol/geom/multipolygon.test.js b/test/spec/ol/geom/multipolygon.test.js
index c45341be7e..4ec851383b 100644
--- a/test/spec/ol/geom/multipolygon.test.js
+++ b/test/spec/ol/geom/multipolygon.test.js
@@ -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]]);
+ });
+ });
+
});
diff --git a/test/spec/ol/geom/polygon.test.js b/test/spec/ol/geom/polygon.test.js
index a600afe1de..0a6345abe4 100644
--- a/test/spec/ol/geom/polygon.test.js
+++ b/test/spec/ol/geom/polygon.test.js
@@ -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];
diff --git a/test/spec/ol/render/canvas/textreplay.test.js b/test/spec/ol/render/canvas/textreplay.test.js
new file mode 100644
index 0000000000..730871f95f
--- /dev/null
+++ b/test/spec/ol/render/canvas/textreplay.test.js
@@ -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);
+ });
+
+});