From 7a3e11b9e41ef960708a6e6751465f65eec60b5a Mon Sep 17 00:00:00 2001 From: Andreas Hocevar Date: Wed, 27 Sep 2017 18:06:05 +0200 Subject: [PATCH 1/3] Change getInteriorPoint type to XYM with intersection length as M --- src/ol/geom/flat/interiorpoint.js | 10 ++++++---- src/ol/geom/multipolygon.js | 5 +++-- src/ol/geom/polygon.js | 5 +++-- test/spec/ol/geom/multipolygon.test.js | 14 ++++++++++++++ test/spec/ol/geom/polygon.test.js | 11 +++++++++++ 5 files changed, 37 insertions(+), 8 deletions(-) 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/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]; From 325fac68865c087904dd0a76f55b5b60c82bc26b Mon Sep 17 00:00:00 2001 From: Andreas Hocevar Date: Wed, 27 Sep 2017 18:06:30 +0200 Subject: [PATCH 2/3] Consider polygon labels only when they fit the intersection length --- changelog/upgrade-notes.md | 4 ++ externs/olx.js | 5 +- src/ol/render/canvas/textreplay.js | 34 +++++++++---- test/spec/ol/render/canvas/textreplay.test.js | 48 +++++++++++++++++++ 4 files changed, 80 insertions(+), 11 deletions(-) create mode 100644 test/spec/ol/render/canvas/textreplay.test.js 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/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/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/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); + }); + +}); From 50568a63946951b9ce5a41f9b5332ad31abb7345 Mon Sep 17 00:00:00 2001 From: Andreas Hocevar Date: Wed, 27 Sep 2017 18:06:57 +0200 Subject: [PATCH 3/3] Simplify vector-layer example and always render labels --- examples/vector-layer.html | 4 +-- examples/vector-layer.js | 61 ++++++++++++++++---------------------- 2 files changed, 27 insertions(+), 38 deletions(-) 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; } });