From 7e940e618edb289fbad55492348b56f841e57562 Mon Sep 17 00:00:00 2001 From: Andreas Hocevar Date: Mon, 18 Apr 2016 10:41:00 +0200 Subject: [PATCH] Introduce new overlaps option for Vector and VectorTile sources Instead of deciding whether to batch fills and strokes by looking at the opacity of the style, we now rely on user input. --- examples/osm-vector-tiles.js | 1 + examples/topojson.js | 3 +- externs/olx.js | 24 +++++++++++ src/ol/color.js | 10 ----- src/ol/render/canvas/replay.js | 48 ++++++++++++--------- src/ol/renderer/canvas/vectorlayer.js | 2 +- src/ol/renderer/canvas/vectortilelayer.js | 2 +- src/ol/renderer/dom/vectorlayer.js | 2 +- src/ol/source/imagevector.js | 2 +- src/ol/source/vector.js | 14 ++++++ src/ol/source/vectortile.js | 14 ++++++ test.html | 33 ++++++++++++++ test/spec/ol/color.test.js | 14 ------ test/spec/ol/renderer/canvas/replay.test.js | 21 ++++----- test_rendering/spec/ol/layer/vector.test.js | 3 ++ 15 files changed, 131 insertions(+), 62 deletions(-) create mode 100644 test.html diff --git a/examples/osm-vector-tiles.js b/examples/osm-vector-tiles.js index db47ac2054..ed965ec7c7 100644 --- a/examples/osm-vector-tiles.js +++ b/examples/osm-vector-tiles.js @@ -93,6 +93,7 @@ var map = new ol.Map({ new ol.layer.VectorTile({ source: new ol.source.VectorTile({ format: format, + overlaps: false, tileGrid: tileGrid, url: 'http://{a-c}.tile.openstreetmap.us/' + 'vectiles-land-usages/{z}/{x}/{y}.topojson' diff --git a/examples/topojson.js b/examples/topojson.js index 551636788a..2373203887 100644 --- a/examples/topojson.js +++ b/examples/topojson.js @@ -29,7 +29,8 @@ var style = new ol.style.Style({ var vector = new ol.layer.Vector({ source: new ol.source.Vector({ url: 'data/topojson/world-110m.json', - format: new ol.format.TopoJSON() + format: new ol.format.TopoJSON(), + overlaps: false }), style: function(feature) { // don't want to render the full world polygon, which repeats all countries diff --git a/externs/olx.js b/externs/olx.js index b009fdaaee..5d3c1fea97 100644 --- a/externs/olx.js +++ b/externs/olx.js @@ -4366,6 +4366,7 @@ olx.source.TileImageOptions.prototype.wrapX; * cacheSize: (number|undefined), * format: (ol.format.Feature|undefined), * logo: (string|olx.LogoOptions|undefined), + * overlaps: (boolean|undefined), * projection: ol.ProjectionLike, * state: (ol.source.State|undefined), * tileClass: (function(new: ol.VectorTile, ol.TileCoord, @@ -4415,6 +4416,17 @@ olx.source.VectorTileOptions.prototype.format; olx.source.VectorTileOptions.prototype.logo; +/** + * This source may have overlapping geometries. Default is `true`. Setting this + * to `false` (e.g. for sources with polygons that represent adminstrative + * boundaries or TopoJSON sources) allows the renderer to optimise fill and + * stroke operations. + * @type {boolean|undefined} + * @api + */ +olx.source.VectorTileOptions.prototype.overlaps; + + /** * Projection. * @type {ol.ProjectionLike} @@ -5797,6 +5809,7 @@ olx.source.TileWMSOptions.prototype.wrapX; * format: (ol.format.Feature|undefined), * loader: (ol.FeatureLoader|undefined), * logo: (string|olx.LogoOptions|undefined), + * overlaps: (boolean|undefined), * strategy: (ol.LoadingStrategy|undefined), * url: (string|ol.FeatureUrlFunction|undefined), * useSpatialIndex: (boolean|undefined), @@ -5849,6 +5862,17 @@ olx.source.VectorOptions.prototype.loader; olx.source.VectorOptions.prototype.logo; +/** + * This source may have overlapping geometries. Default is `true`. Setting this + * to `false` (e.g. for sources with polygons that represent adminstrative + * boundaries or TopoJSON sources) allows the renderer to optimise fill and + * stroke operations. + * @type {boolean|undefined} + * @api + */ +olx.source.VectorOptions.prototype.overlaps; + + /** * The loading strategy to use. By default an {@link ol.loadingstrategy.all} * strategy is used, a one-off strategy which loads all features at once. diff --git a/src/ol/color.js b/src/ol/color.js index e27041a307..338a23446b 100644 --- a/src/ol/color.js +++ b/src/ol/color.js @@ -190,16 +190,6 @@ ol.color.fromStringInternal_ = function(s) { }; -/** - * @param {ol.ColorLike|string} color Color. - * @return {boolean} Is rgba. - */ -ol.color.isRgba = function(color) { - return Array.isArray(color) && color.length == 4 || - typeof color == 'string' && ol.color.rgbaColorRe_.test(color); -}; - - /** * @param {ol.Color} color Color. * @return {boolean} Is valid. diff --git a/src/ol/render/canvas/replay.js b/src/ol/render/canvas/replay.js index 0891bc12e9..92b4a758b5 100644 --- a/src/ol/render/canvas/replay.js +++ b/src/ol/render/canvas/replay.js @@ -52,10 +52,11 @@ ol.render.canvas.Instruction = { * @param {number} tolerance Tolerance. * @param {ol.Extent} maxExtent Maximum extent. * @param {number} resolution Resolution. + * @param {boolean} overlaps The replay can have overlapping geometries. * @protected * @struct */ -ol.render.canvas.Replay = function(tolerance, maxExtent, resolution) { +ol.render.canvas.Replay = function(tolerance, maxExtent, resolution, overlaps) { ol.render.VectorContext.call(this); /** @@ -75,7 +76,7 @@ ol.render.canvas.Replay = function(tolerance, maxExtent, resolution) { * @protected * @type {boolean} */ - this.transparency = false; + this.overlaps = overlaps; /** * @private @@ -266,7 +267,7 @@ ol.render.canvas.Replay.prototype.replay_ = function( var pendingFill = 0; var pendingStroke = 0; var batchSize = - this.instructions != instructions || this.transparency ? 0 : 200; + this.instructions != instructions || this.overlaps ? 0 : 200; while (i < ii) { var instruction = instructions[i]; var type = /** @type {ol.render.canvas.Instruction} */ (instruction[0]); @@ -691,11 +692,12 @@ ol.render.canvas.Replay.prototype.getBufferedMaxExtent = function() { * @param {number} tolerance Tolerance. * @param {ol.Extent} maxExtent Maximum extent. * @param {number} resolution Resolution. + * @param {boolean} overlaps The replay can have overlapping geometries. * @protected * @struct */ -ol.render.canvas.ImageReplay = function(tolerance, maxExtent, resolution) { - ol.render.canvas.Replay.call(this, tolerance, maxExtent, resolution); +ol.render.canvas.ImageReplay = function(tolerance, maxExtent, resolution, overlaps) { + ol.render.canvas.Replay.call(this, tolerance, maxExtent, resolution, overlaps); /** * @private @@ -957,12 +959,13 @@ ol.render.canvas.ImageReplay.prototype.setImageStyle = function(imageStyle) { * @param {number} tolerance Tolerance. * @param {ol.Extent} maxExtent Maximum extent. * @param {number} resolution Resolution. + * @param {boolean} overlaps The replay can have overlapping geometries. * @protected * @struct */ -ol.render.canvas.LineStringReplay = function(tolerance, maxExtent, resolution) { +ol.render.canvas.LineStringReplay = function(tolerance, maxExtent, resolution, overlaps) { - ol.render.canvas.Replay.call(this, tolerance, maxExtent, resolution); + ol.render.canvas.Replay.call(this, tolerance, maxExtent, resolution, overlaps); /** * @private @@ -1191,12 +1194,13 @@ ol.render.canvas.LineStringReplay.prototype.setFillStrokeStyle = function(fillSt * @param {number} tolerance Tolerance. * @param {ol.Extent} maxExtent Maximum extent. * @param {number} resolution Resolution. + * @param {boolean} overlaps The replay can have overlapping geometries. * @protected * @struct */ -ol.render.canvas.PolygonReplay = function(tolerance, maxExtent, resolution) { +ol.render.canvas.PolygonReplay = function(tolerance, maxExtent, resolution, overlaps) { - ol.render.canvas.Replay.call(this, tolerance, maxExtent, resolution); + ol.render.canvas.Replay.call(this, tolerance, maxExtent, resolution, overlaps); /** * @private @@ -1457,10 +1461,6 @@ ol.render.canvas.PolygonReplay.prototype.setFillStrokeStyle = function(fillStyle var fillStyleColor = fillStyle.getColor(); state.fillStyle = ol.colorlike.asColorLike(fillStyleColor ? fillStyleColor : ol.render.canvas.defaultFillStyle); - if (!this.transparency && ol.color.isRgba(state.fillStyle)) { - this.transparency = ol.color.asArray( - /** @type {ol.Color|string} */ (state.fillStyle))[0] != 1; - } } else { state.fillStyle = undefined; } @@ -1468,9 +1468,6 @@ ol.render.canvas.PolygonReplay.prototype.setFillStrokeStyle = function(fillStyle var strokeStyleColor = strokeStyle.getColor(); state.strokeStyle = ol.color.asString(strokeStyleColor ? strokeStyleColor : ol.render.canvas.defaultStrokeStyle); - if (!this.transparency && ol.color.isRgba(state.strokeStyle)) { - this.transparency = ol.color.asArray(state.strokeStyle)[3] != 1; - } var strokeStyleLineCap = strokeStyle.getLineCap(); state.lineCap = strokeStyleLineCap !== undefined ? strokeStyleLineCap : ol.render.canvas.defaultLineCap; @@ -1553,12 +1550,13 @@ ol.render.canvas.PolygonReplay.prototype.setFillStrokeStyles_ = function() { * @param {number} tolerance Tolerance. * @param {ol.Extent} maxExtent Maximum extent. * @param {number} resolution Resolution. + * @param {boolean} overlaps The replay can have overlapping geometries. * @protected * @struct */ -ol.render.canvas.TextReplay = function(tolerance, maxExtent, resolution) { +ol.render.canvas.TextReplay = function(tolerance, maxExtent, resolution, overlaps) { - ol.render.canvas.Replay.call(this, tolerance, maxExtent, resolution); + ol.render.canvas.Replay.call(this, tolerance, maxExtent, resolution, overlaps); /** * @private @@ -1862,10 +1860,12 @@ ol.render.canvas.TextReplay.prototype.setTextStyle = function(textStyle) { * @param {number} tolerance Tolerance. * @param {ol.Extent} maxExtent Max extent. * @param {number} resolution Resolution. + * @param {boolean} overlaps The replay group can have overlapping geometries. * @param {number=} opt_renderBuffer Optional rendering buffer. * @struct */ -ol.render.canvas.ReplayGroup = function(tolerance, maxExtent, resolution, opt_renderBuffer) { +ol.render.canvas.ReplayGroup = function( + tolerance, maxExtent, resolution, overlaps, opt_renderBuffer) { ol.render.ReplayGroup.call(this); /** @@ -1880,6 +1880,12 @@ ol.render.canvas.ReplayGroup = function(tolerance, maxExtent, resolution, opt_re */ this.maxExtent_ = maxExtent; + /** + * @private + * @type {boolean} + */ + this.overlaps_ = overlaps; + /** * @private * @type {number} @@ -1999,7 +2005,7 @@ ol.render.canvas.ReplayGroup.prototype.getReplay = function(zIndex, replayType) replayType + ' constructor missing from ol.render.canvas.BATCH_CONSTRUCTORS_'); replay = new Constructor(this.tolerance_, this.maxExtent_, - this.resolution_); + this.resolution_, this.overlaps_); replays[replayType] = replay; } return replay; @@ -2113,7 +2119,7 @@ ol.render.canvas.ReplayGroup.prototype.replayHitDetection_ = function( * @private * @type {Object.} + * number, boolean)>} */ ol.render.canvas.BATCH_CONSTRUCTORS_ = { 'Image': ol.render.canvas.ImageReplay, diff --git a/src/ol/renderer/canvas/vectorlayer.js b/src/ol/renderer/canvas/vectorlayer.js index f48a23ee2a..e5240cc8d3 100644 --- a/src/ol/renderer/canvas/vectorlayer.js +++ b/src/ol/renderer/canvas/vectorlayer.js @@ -271,7 +271,7 @@ ol.renderer.canvas.VectorLayer.prototype.prepareFrame = function(frameState, lay var replayGroup = new ol.render.canvas.ReplayGroup( ol.renderer.vector.getTolerance(resolution, pixelRatio), extent, - resolution, vectorLayer.getRenderBuffer()); + resolution, vectorSource.getOverlaps(), vectorLayer.getRenderBuffer()); vectorSource.loadFeatures(extent, resolution, projection); /** * @param {ol.Feature} feature Feature. diff --git a/src/ol/renderer/canvas/vectortilelayer.js b/src/ol/renderer/canvas/vectortilelayer.js index 3f41dc906c..0680020a75 100644 --- a/src/ol/renderer/canvas/vectortilelayer.js +++ b/src/ol/renderer/canvas/vectortilelayer.js @@ -207,7 +207,7 @@ ol.renderer.canvas.VectorTileLayer.prototype.createReplayGroup = function(tile, } replayState.dirty = false; var replayGroup = new ol.render.canvas.ReplayGroup(0, extent, - tileResolution, layer.getRenderBuffer()); + tileResolution, source.getOverlaps(), layer.getRenderBuffer()); var squaredTolerance = ol.renderer.vector.getSquaredTolerance( tileResolution, pixelRatio); diff --git a/src/ol/renderer/dom/vectorlayer.js b/src/ol/renderer/dom/vectorlayer.js index 47ddc19eb0..ef98e95b2c 100644 --- a/src/ol/renderer/dom/vectorlayer.js +++ b/src/ol/renderer/dom/vectorlayer.js @@ -259,7 +259,7 @@ ol.renderer.dom.VectorLayer.prototype.prepareFrame = function(frameState, layerS var replayGroup = new ol.render.canvas.ReplayGroup( ol.renderer.vector.getTolerance(resolution, pixelRatio), extent, - resolution, vectorLayer.getRenderBuffer()); + resolution, vectorSource.getOverlaps(), vectorLayer.getRenderBuffer()); vectorSource.loadFeatures(extent, resolution, projection); /** * @param {ol.Feature} feature Feature. diff --git a/src/ol/source/imagevector.js b/src/ol/source/imagevector.js index ae5a5f8937..2eac2bccb0 100644 --- a/src/ol/source/imagevector.js +++ b/src/ol/source/imagevector.js @@ -113,7 +113,7 @@ ol.source.ImageVector.prototype.canvasFunctionInternal_ = function(extent, resol var replayGroup = new ol.render.canvas.ReplayGroup( ol.renderer.vector.getTolerance(resolution, pixelRatio), extent, - resolution, this.renderBuffer_); + resolution, this.source_.getOverlaps(), this.renderBuffer_); this.source_.loadFeatures(extent, resolution, projection); diff --git a/src/ol/source/vector.js b/src/ol/source/vector.js index a12c7bb089..4930fe15c6 100644 --- a/src/ol/source/vector.js +++ b/src/ol/source/vector.js @@ -94,6 +94,12 @@ ol.source.Vector = function(opt_options) { */ this.format_ = options.format; + /** + * @private + * @type {boolean} + */ + this.overlaps_ = options.overlaps == undefined ? true : options.overlaps; + /** * @private * @type {string|ol.FeatureUrlFunction|undefined} @@ -695,6 +701,14 @@ ol.source.Vector.prototype.getFormat = function() { }; +/** + * @return {boolean} The source can have overlapping geometries. + */ +ol.source.Vector.prototype.getOverlaps = function() { + return this.overlaps_; +}; + + /** * Get the url associated with this source. * diff --git a/src/ol/source/vectortile.js b/src/ol/source/vectortile.js index 49a34b6ca4..b9e003ddb6 100644 --- a/src/ol/source/vectortile.js +++ b/src/ol/source/vectortile.js @@ -52,6 +52,12 @@ ol.source.VectorTile = function(options) { */ this.format_ = options.format ? options.format : null; + /** + * @private + * @type {boolean} + */ + this.overlaps_ = options.overlaps || true; + /** * @protected * @type {function(new: ol.VectorTile, ol.TileCoord, ol.Tile.State, string, @@ -63,6 +69,14 @@ ol.source.VectorTile = function(options) { ol.inherits(ol.source.VectorTile, ol.source.UrlTile); +/** + * @return {boolean} The source can have overlapping geometries. + */ +ol.source.VectorTile.prototype.getOverlaps = function() { + return this.overlaps_; +}; + + /** * @inheritDoc */ diff --git a/test.html b/test.html new file mode 100644 index 0000000000..ab049dd51a --- /dev/null +++ b/test.html @@ -0,0 +1,33 @@ + + + + + + OpenLayers 3 example + + + +

My Map

+
+ + + + diff --git a/test/spec/ol/color.test.js b/test/spec/ol/color.test.js index 9c0a4e9af4..0df4432ed0 100644 --- a/test/spec/ol/color.test.js +++ b/test/spec/ol/color.test.js @@ -103,20 +103,6 @@ describe('ol.color', function() { }); - describe('ol.color.isRgba', function() { - it('identifies rgba arrays', function() { - expect(ol.color.isRgba([255, 255, 255, 1])).to.be(true); - expect(ol.color.isRgba([255, 255, 255])).to.be(false); - }); - it('identifies rgba strings', function() { - expect(ol.color.isRgba('rgba(255,255,255,1)')).to.be(true); - expect(ol.color.isRgba('rgb(255,255,255)')).to.be(false); - expect(ol.color.isRgba('#FFF')).to.be(false); - expect(ol.color.isRgba('#FFFFFF')).to.be(false); - expect(ol.color.isRgba('red')).to.be(false); - }); - }); - describe('ol.color.isValid', function() { it('identifies valid colors', function() { diff --git a/test/spec/ol/renderer/canvas/replay.test.js b/test/spec/ol/renderer/canvas/replay.test.js index 1f30b88ebc..496e8de250 100644 --- a/test/spec/ol/renderer/canvas/replay.test.js +++ b/test/spec/ol/renderer/canvas/replay.test.js @@ -5,11 +5,11 @@ describe('ol.render.canvas.ReplayGroup', function() { describe('#replay', function() { var context, replay, fillCount, strokeCount, beginPathCount; - var feature1, feature2, feature3, style1, style2, style3, transform; + var feature1, feature2, feature3, style1, style2, transform; beforeEach(function() { transform = goog.vec.Mat4.createNumber(); - replay = new ol.render.canvas.ReplayGroup(1, [-180, -90, 180, 90], 1); + replay = new ol.render.canvas.ReplayGroup(1, [-180, -90, 180, 90], 1, false); feature1 = new ol.Feature(new ol.geom.Polygon( [[[-90, -45], [-90, 0], [0, 0], [0, -45], [-90, -45]]])); feature2 = new ol.Feature(new ol.geom.Polygon( @@ -24,10 +24,6 @@ describe('ol.render.canvas.ReplayGroup', function() { fill: new ol.style.Fill({color: 'white'}), stroke: new ol.style.Stroke({color: 'black', width: 1}) }); - style3 = new ol.style.Style({ - fill: new ol.style.Fill({color: 'rgba(255,255,255,0.8)'}), - stroke: new ol.style.Stroke({color: 'rgba(0,0,0,0.8)', width: 1}) - }); fillCount = 0; strokeCount = 0; beginPathCount = 0; @@ -121,10 +117,11 @@ describe('ol.render.canvas.ReplayGroup', function() { expect(beginPathCount).to.be(1); }); - it('does not batch when transparent fills/strokes are used', function() { - ol.renderer.vector.renderFeature(replay, feature1, style3, 1); - ol.renderer.vector.renderFeature(replay, feature2, style3, 1); - ol.renderer.vector.renderFeature(replay, feature3, style3, 1); + it('does not batch when overlaps is set to true', function() { + replay = new ol.render.canvas.ReplayGroup(1, [-180, -90, 180, 90], 1, true); + ol.renderer.vector.renderFeature(replay, feature1, style1, 1); + ol.renderer.vector.renderFeature(replay, feature2, style1, 1); + ol.renderer.vector.renderFeature(replay, feature3, style1, 1); replay.replay(context, 1, transform, 0, {}); expect(fillCount).to.be(3); expect(strokeCount).to.be(3); @@ -141,7 +138,7 @@ describe('ol.render.canvas.Replay', function() { it('creates a new replay batch', function() { var tolerance = 10; var extent = [-180, -90, 180, 90]; - var replay = new ol.render.canvas.Replay(tolerance, extent, 1); + var replay = new ol.render.canvas.Replay(tolerance, extent, 1, true); expect(replay).to.be.a(ol.render.canvas.Replay); }); @@ -151,7 +148,7 @@ describe('ol.render.canvas.Replay', function() { var replay; beforeEach(function() { - replay = new ol.render.canvas.Replay(1, [-180, -90, 180, 90], 1); + replay = new ol.render.canvas.Replay(1, [-180, -90, 180, 90], 1, true); }); it('appends coordinates that are within the max extent', function() { diff --git a/test_rendering/spec/ol/layer/vector.test.js b/test_rendering/spec/ol/layer/vector.test.js index 81eddfe38a..8a2fb7224d 100644 --- a/test_rendering/spec/ol/layer/vector.test.js +++ b/test_rendering/spec/ol/layer/vector.test.js @@ -87,6 +87,9 @@ describe('ol.rendering.layer.Vector', function() { it('renders fill/stroke batches correctly with the canvas renderer', function(done) { map = createMap('canvas'); + source = new ol.source.Vector({ + overlaps: false + }); addPolygon(100); addCircle(200); addPolygon(250);