Merge pull request #7292 from ahocevar/polygon-labels

Only render polygon labels when they fit
This commit is contained in:
Andreas Hocevar
2017-09-27 22:55:47 +02:00
committed by GitHub
11 changed files with 144 additions and 57 deletions

View File

@@ -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`.

View File

@@ -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">&nbsp;</div>

View File

@@ -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;
}
});

View File

@@ -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
*/

View File

@@ -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 = [];

View File

@@ -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;
};

View File

@@ -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);
};

View File

@@ -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,

View File

@@ -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]]);
});
});
});

View File

@@ -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];

View 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);
});
});