From 3c37ce39906ade41d5e4c49043797790cd1d19da 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/mapbox-vector-tiles-advanced.js | 1 + examples/mapbox-vector-tiles.js | 1 + examples/osm-vector-tiles.js | 1 + examples/topojson.js | 3 +- externs/olx.js | 24 ++++++++++ src/ol/color/color.js | 10 ---- src/ol/render/canvas/canvasreplay.js | 47 ++++++++++--------- .../canvas/canvasvectorlayerrenderer.js | 2 +- .../canvas/canvasvectortilelayerrenderer.js | 2 +- src/ol/renderer/dom/domvectorlayerrenderer.js | 2 +- src/ol/source/imagevectorsource.js | 2 +- src/ol/source/vectorsource.js | 14 ++++++ src/ol/source/vectortilesource.js | 14 ++++++ test.html | 33 +++++++++++++ test/spec/ol/color.test.js | 14 ------ .../ol/renderer/canvas/canvasreplay.test.js | 21 ++++----- test_rendering/spec/ol/layer/vector.test.js | 3 ++ 17 files changed, 132 insertions(+), 62 deletions(-) create mode 100644 test.html diff --git a/examples/mapbox-vector-tiles-advanced.js b/examples/mapbox-vector-tiles-advanced.js index b960ad8964..9261d52a01 100644 --- a/examples/mapbox-vector-tiles-advanced.js +++ b/examples/mapbox-vector-tiles-advanced.js @@ -50,6 +50,7 @@ var map = new ol.Map({ '© ' + 'OpenStreetMap contributors', format: new ol.format.MVT(), + overlaps: false, tileGrid: new ol.tilegrid.TileGrid({ extent: ol.proj.get('EPSG:3857').getExtent(), resolutions: resolutions diff --git a/examples/mapbox-vector-tiles.js b/examples/mapbox-vector-tiles.js index ed3688bd4e..da9719a18e 100644 --- a/examples/mapbox-vector-tiles.js +++ b/examples/mapbox-vector-tiles.js @@ -21,6 +21,7 @@ var map = new ol.Map({ '© ' + 'OpenStreetMap contributors', format: new ol.format.MVT(), + overlaps: false, tileGrid: ol.tilegrid.createXYZ({maxZoom: 22}), tilePixelRatio: 16, url: 'http://{a-d}.tiles.mapbox.com/v4/mapbox.mapbox-streets-v6/' + diff --git a/examples/osm-vector-tiles.js b/examples/osm-vector-tiles.js index bef1e300e1..aef7ca7c87 100644 --- a/examples/osm-vector-tiles.js +++ b/examples/osm-vector-tiles.js @@ -92,6 +92,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 61e8fed4e5..f2711e433d 100644 --- a/externs/olx.js +++ b/externs/olx.js @@ -4356,6 +4356,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, @@ -4405,6 +4406,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} @@ -5775,6 +5787,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), @@ -5827,6 +5840,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/color.js b/src/ol/color/color.js index 84ba8e6694..c322252c37 100644 --- a/src/ol/color/color.js +++ b/src/ol/color/color.js @@ -183,16 +183,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/canvasreplay.js b/src/ol/render/canvas/canvasreplay.js index 20066e4c56..97bba2b460 100644 --- a/src/ol/render/canvas/canvasreplay.js +++ b/src/ol/render/canvas/canvasreplay.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,11 +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) { + tolerance, maxExtent, resolution, overlaps, opt_renderBuffer) { /** * @private @@ -1880,6 +1879,12 @@ ol.render.canvas.ReplayGroup = function( */ this.maxExtent_ = maxExtent; + /** + * @private + * @type {boolean} + */ + this.overlaps_ = overlaps; + /** * @private * @type {number} @@ -1998,7 +2003,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; @@ -2112,7 +2117,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/canvasvectorlayerrenderer.js b/src/ol/renderer/canvas/canvasvectorlayerrenderer.js index 18871e438e..0d9b168dd3 100644 --- a/src/ol/renderer/canvas/canvasvectorlayerrenderer.js +++ b/src/ol/renderer/canvas/canvasvectorlayerrenderer.js @@ -264,7 +264,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/canvasvectortilelayerrenderer.js b/src/ol/renderer/canvas/canvasvectortilelayerrenderer.js index d073d947b2..f07a23ed91 100644 --- a/src/ol/renderer/canvas/canvasvectortilelayerrenderer.js +++ b/src/ol/renderer/canvas/canvasvectortilelayerrenderer.js @@ -214,7 +214,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/domvectorlayerrenderer.js b/src/ol/renderer/dom/domvectorlayerrenderer.js index c4b32b9b74..fb1ed4a803 100644 --- a/src/ol/renderer/dom/domvectorlayerrenderer.js +++ b/src/ol/renderer/dom/domvectorlayerrenderer.js @@ -269,7 +269,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/imagevectorsource.js b/src/ol/source/imagevectorsource.js index f7e9324523..c20537aaa9 100644 --- a/src/ol/source/imagevectorsource.js +++ b/src/ol/source/imagevectorsource.js @@ -108,7 +108,7 @@ ol.source.ImageVector.prototype.canvasFunctionInternal_ = function(extent, resol var replayGroup = new ol.render.canvas.ReplayGroup( ol.renderer.vector.getTolerance(resolution, pixelRatio), extent, - resolution); + resolution, this.source_.getOverlaps()); this.source_.loadFeatures(extent, resolution, projection); diff --git a/src/ol/source/vectorsource.js b/src/ol/source/vectorsource.js index 88f637cea9..260855b31f 100644 --- a/src/ol/source/vectorsource.js +++ b/src/ol/source/vectorsource.js @@ -96,6 +96,12 @@ ol.source.Vector = function(opt_options) { */ this.format_ = options.format; + /** + * @private + * @type {boolean} + */ + this.overlaps_ = options.overlaps || true; + /** * @private * @type {string|ol.FeatureUrlFunction|undefined} @@ -704,6 +710,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/vectortilesource.js b/src/ol/source/vectortilesource.js index b3fc3b5ee3..57cbf3c391 100644 --- a/src/ol/source/vectortilesource.js +++ b/src/ol/source/vectortilesource.js @@ -51,6 +51,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.TileState, string, @@ -62,6 +68,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 12bcbdb3d5..d15e761d15 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/canvasreplay.test.js b/test/spec/ol/renderer/canvas/canvasreplay.test.js index 1f30b88ebc..496e8de250 100644 --- a/test/spec/ol/renderer/canvas/canvasreplay.test.js +++ b/test/spec/ol/renderer/canvas/canvasreplay.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 f8e4c6bf8b..94738027d9 100644 --- a/test_rendering/spec/ol/layer/vector.test.js +++ b/test_rendering/spec/ol/layer/vector.test.js @@ -75,6 +75,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);