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 4337c37885..427da9db83 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 190ba3fb45..ea0dc65fa3 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 administrative + * 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 administrative + * 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/render/canvas/replay.js b/src/ol/render/canvas/replay.js index 9b3e14440a..e989321cfa 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); /** @@ -71,6 +72,12 @@ ol.render.canvas.Replay = function(tolerance, maxExtent, resolution) { */ this.maxExtent = maxExtent; + /** + * @protected + * @type {boolean} + */ + this.overlaps = overlaps; + /** * @private * @type {ol.Extent} @@ -257,6 +264,12 @@ ol.render.canvas.Replay.prototype.replay_ = function( var localTransform = this.tmpLocalTransform_; var localTransformInv = this.tmpLocalTransformInv_; var prevX, prevY, roundX, roundY; + var pendingFill = 0; + var pendingStroke = 0; + // When the batch size gets too big, performance decreases. 200 is a good + // balance between batch size and number of fill/stroke instructions. + var batchSize = + this.instructions != instructions || this.overlaps ? 0 : 200; while (i < ii) { var instruction = instructions[i]; var type = /** @type {ol.render.canvas.Instruction} */ (instruction[0]); @@ -276,7 +289,17 @@ ol.render.canvas.Replay.prototype.replay_ = function( } break; case ol.render.canvas.Instruction.BEGIN_PATH: - context.beginPath(); + if (pendingFill > batchSize) { + context.fill(); + pendingFill = 0; + } + if (pendingStroke > batchSize) { + context.stroke(); + pendingStroke = 0; + } + if (!pendingFill && !pendingStroke) { + context.beginPath(); + } ++i; break; case ol.render.canvas.Instruction.CIRCLE: @@ -290,6 +313,7 @@ ol.render.canvas.Replay.prototype.replay_ = function( var dx = x2 - x1; var dy = y2 - y1; var r = Math.sqrt(dx * dx + dy * dy); + context.moveTo(x2, y2); context.arc(x1, y1, r, 0, 2 * Math.PI, true); ++i; break; @@ -442,7 +466,11 @@ ol.render.canvas.Replay.prototype.replay_ = function( ++i; break; case ol.render.canvas.Instruction.FILL: - context.fill(); + if (batchSize) { + pendingFill++; + } else { + context.fill(); + } ++i; break; case ol.render.canvas.Instruction.MOVE_TO_LINE_TO: @@ -479,6 +507,11 @@ ol.render.canvas.Replay.prototype.replay_ = function( ol.colorlike.isColorLike(instruction[1]), '2nd instruction should be a string, ' + 'CanvasPattern, or CanvasGradient'); + if (pendingFill) { + context.fill(); + pendingFill = 0; + } + context.fillStyle = /** @type {ol.ColorLike} */ (instruction[1]); ++i; break; @@ -498,6 +531,10 @@ ol.render.canvas.Replay.prototype.replay_ = function( var usePixelRatio = instruction[7] !== undefined ? instruction[7] : true; var lineWidth = /** @type {number} */ (instruction[2]); + if (pendingStroke) { + context.stroke(); + pendingStroke = 0; + } context.strokeStyle = /** @type {string} */ (instruction[1]); context.lineWidth = usePixelRatio ? lineWidth * pixelRatio : lineWidth; context.lineCap = /** @type {string} */ (instruction[3]); @@ -523,7 +560,11 @@ ol.render.canvas.Replay.prototype.replay_ = function( ++i; break; case ol.render.canvas.Instruction.STROKE: - context.stroke(); + if (batchSize) { + pendingStroke++; + } else { + context.stroke(); + } ++i; break; default: @@ -532,6 +573,12 @@ ol.render.canvas.Replay.prototype.replay_ = function( break; } } + if (pendingFill) { + context.fill(); + } + if (pendingStroke) { + context.stroke(); + } // assert that all instructions were consumed goog.DEBUG && console.assert(i == instructions.length, 'all instructions should be consumed'); @@ -551,7 +598,7 @@ ol.render.canvas.Replay.prototype.replay = function( context, pixelRatio, transform, viewRotation, skippedFeaturesHash) { var instructions = this.instructions; this.replay_(context, pixelRatio, transform, viewRotation, - skippedFeaturesHash, instructions, undefined); + skippedFeaturesHash, instructions, undefined, undefined); }; @@ -651,11 +698,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 @@ -917,12 +965,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 @@ -1151,12 +1200,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 @@ -1223,8 +1273,6 @@ ol.render.canvas.PolygonReplay.prototype.drawFlatCoordinatess_ = function(flatCo closePathInstruction); offset = end; } - // FIXME is it quicker to fill and stroke each polygon individually, - // FIXME or all polygons together? var fillInstruction = [ol.render.canvas.Instruction.FILL]; this.hitDetectionInstructions.push(fillInstruction); if (state.fillStyle !== undefined) { @@ -1506,12 +1554,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 @@ -1823,10 +1872,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); /** @@ -1841,6 +1892,12 @@ ol.render.canvas.ReplayGroup = function(tolerance, maxExtent, resolution, opt_re */ this.maxExtent_ = maxExtent; + /** + * @private + * @type {boolean} + */ + this.overlaps_ = overlaps; + /** * @private * @type {number} @@ -1960,7 +2017,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; @@ -2074,7 +2131,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 0525478346..f3164df2bb 100644 --- a/src/ol/renderer/canvas/vectorlayer.js +++ b/src/ol/renderer/canvas/vectorlayer.js @@ -280,7 +280,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 bd86581aec..ce25d4eff3 100644 --- a/src/ol/renderer/canvas/vectortilelayer.js +++ b/src/ol/renderer/canvas/vectortilelayer.js @@ -220,7 +220,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 8f5e0949db..e82de85fc8 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..a8f0b42590 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 == undefined ? true : options.overlaps; + /** * @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/spec/ol/renderer/canvas/replay.test.js b/test/spec/ol/renderer/canvas/replay.test.js index bace66747d..1585fed1d5 100644 --- a/test/spec/ol/renderer/canvas/replay.test.js +++ b/test/spec/ol/renderer/canvas/replay.test.js @@ -1,10 +1,147 @@ goog.provide('ol.test.renderer.canvas.Replay'); +goog.require('ol.transform'); +goog.require('ol.Feature'); +goog.require('ol.geom.Polygon'); goog.require('ol.render.canvas.LineStringReplay'); goog.require('ol.render.canvas.PolygonReplay'); goog.require('ol.render.canvas.Replay'); +goog.require('ol.render.canvas.ReplayGroup'); +goog.require('ol.renderer.vector'); +goog.require('ol.style.Fill'); goog.require('ol.style.Stroke'); +goog.require('ol.style.Style'); +describe('ol.render.canvas.ReplayGroup', function() { + + describe('#replay', function() { + + var context, replay, fillCount, strokeCount, beginPathCount; + var feature1, feature2, feature3, style1, style2, transform; + + beforeEach(function() { + transform = ol.transform.create(); + 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( + [[[90, 45], [90, 0], [0, 0], [0, 45], [90, 45]]])); + feature3 = new ol.Feature(new ol.geom.Polygon( + [[[-90, -45], [-90, 45], [90, 45], [90, -45], [-90, -45]]])); + style1 = new ol.style.Style({ + fill: new ol.style.Fill({color: 'black'}), + stroke: new ol.style.Stroke({color: 'white', width: 1}) + }); + style2 = new ol.style.Style({ + fill: new ol.style.Fill({color: 'white'}), + stroke: new ol.style.Stroke({color: 'black', width: 1}) + }); + fillCount = 0; + strokeCount = 0; + beginPathCount = 0; + context = { + fill: function() { + fillCount++; + }, + stroke: function() { + strokeCount++; + }, + beginPath: function() { + beginPathCount++; + }, + clip: function() { + beginPathCount--; + }, + save: function() {}, + moveTo: function() {}, + lineTo: function() {}, + closePath: function() {}, + setLineDash: function() {}, + restore: function() {} + }; + + }); + + it('batches fill and stroke instructions for same style', function() { + 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(1); + expect(strokeCount).to.be(1); + expect(beginPathCount).to.be(1); + }); + + it('batches fill and stroke instructions for different styles', function() { + ol.renderer.vector.renderFeature(replay, feature1, style1, 1); + ol.renderer.vector.renderFeature(replay, feature2, style1, 1); + ol.renderer.vector.renderFeature(replay, feature3, style2, 1); + replay.replay(context, 1, transform, 0, {}); + expect(fillCount).to.be(2); + expect(strokeCount).to.be(2); + expect(beginPathCount).to.be(2); + }); + + it('batches fill and stroke instructions for changing styles', function() { + ol.renderer.vector.renderFeature(replay, feature1, style1, 1); + ol.renderer.vector.renderFeature(replay, feature2, style2, 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); + expect(beginPathCount).to.be(3); + }); + + it('batches fill and stroke instructions for skipped feature at the beginning', function() { + ol.renderer.vector.renderFeature(replay, feature1, style1, 1); + ol.renderer.vector.renderFeature(replay, feature2, style2, 1); + ol.renderer.vector.renderFeature(replay, feature3, style2, 1); + var skippedUids = {}; + skippedUids[ol.getUid(feature1)] = true; + replay.replay(context, 1, transform, 0, skippedUids); + expect(fillCount).to.be(1); + expect(strokeCount).to.be(1); + expect(beginPathCount).to.be(1); + }); + + it('batches fill and stroke instructions for skipped feature at the end', function() { + ol.renderer.vector.renderFeature(replay, feature1, style1, 1); + ol.renderer.vector.renderFeature(replay, feature2, style1, 1); + ol.renderer.vector.renderFeature(replay, feature3, style2, 1); + var skippedUids = {}; + skippedUids[ol.getUid(feature3)] = true; + replay.replay(context, 1, transform, 0, skippedUids); + expect(fillCount).to.be(1); + expect(strokeCount).to.be(1); + expect(beginPathCount).to.be(1); + }); + + it('batches fill and stroke instructions for skipped features', function() { + ol.renderer.vector.renderFeature(replay, feature1, style1, 1); + ol.renderer.vector.renderFeature(replay, feature2, style1, 1); + ol.renderer.vector.renderFeature(replay, feature3, style2, 1); + var skippedUids = {}; + skippedUids[ol.getUid(feature1)] = true; + skippedUids[ol.getUid(feature2)] = true; + replay.replay(context, 1, transform, 0, skippedUids); + expect(fillCount).to.be(1); + expect(strokeCount).to.be(1); + expect(beginPathCount).to.be(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); + expect(beginPathCount).to.be(3); + }); + }); + +}); describe('ol.render.canvas.Replay', function() { @@ -13,7 +150,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); }); @@ -23,7 +160,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/expected/vector-canvas-opaque.png b/test_rendering/spec/ol/layer/expected/vector-canvas-opaque.png new file mode 100644 index 0000000000..5781ffd7ed Binary files /dev/null and b/test_rendering/spec/ol/layer/expected/vector-canvas-opaque.png differ diff --git a/test_rendering/spec/ol/layer/expected/vector-canvas-stroke.png b/test_rendering/spec/ol/layer/expected/vector-canvas-stroke.png new file mode 100644 index 0000000000..9627e9c34f Binary files /dev/null and b/test_rendering/spec/ol/layer/expected/vector-canvas-stroke.png differ diff --git a/test_rendering/spec/ol/layer/vector.test.js b/test_rendering/spec/ol/layer/vector.test.js index 64d480bf5b..a64903069b 100644 --- a/test_rendering/spec/ol/layer/vector.test.js +++ b/test_rendering/spec/ol/layer/vector.test.js @@ -8,6 +8,7 @@ goog.require('ol.geom.LineString'); goog.require('ol.geom.Polygon'); goog.require('ol.layer.Vector'); goog.require('ol.source.Vector'); +goog.require('ol.style.Fill'); goog.require('ol.style.Stroke'); goog.require('ol.style.Style'); @@ -49,6 +50,16 @@ describe('ol.rendering.layer.Vector', function() { ]))); } + function addLineString(r) { + source.addFeature(new ol.Feature(new ol.geom.LineString([ + [center[0] - r, center[1] - r], + [center[0] + r, center[1] - r], + [center[0] + r, center[1] + r], + [center[0] - r, center[1] + r], + [center[0] - r, center[1] - r] + ]))); + } + describe('vector layer', function() { beforeEach(function() { @@ -59,7 +70,7 @@ describe('ol.rendering.layer.Vector', function() { disposeMap(map); }); - it('renders correctly with the canvas renderer', function(done) { + it('renders opacity correctly with the canvas renderer', function(done) { map = createMap('canvas'); var smallLine = new ol.Feature(new ol.geom.LineString([ [center[0], center[1] - 1], @@ -85,6 +96,159 @@ 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); + addCircle(500); + addPolygon(600); + addPolygon(720); + map.addLayer(new ol.layer.Vector({ + source: source, + style: new ol.style.Style({ + stroke: new ol.style.Stroke({ + color: '#3399CC', + width: 1.25 + }) + }) + })); + map.once('postrender', function() { + expectResemble(map, 'spec/ol/layer/expected/vector-canvas-opaque.png', + 17, done); + }); + }); + + it('renders stroke batches correctly with the canvas renderer', function(done) { + map = createMap('canvas'); + source = new ol.source.Vector({ + overlaps: false + }); + addLineString(100); + addLineString(250); + addLineString(600); + addLineString(720); + map.addLayer(new ol.layer.Vector({ + source: source, + style: new ol.style.Style({ + stroke: new ol.style.Stroke({ + color: '#3399CC', + width: 1.25 + }) + }) + })); + map.once('postrender', function() { + expectResemble(map, 'spec/ol/layer/expected/vector-canvas-stroke.png', + 7, done); + }); + }); + + it('interrupts fill/stroke batches correctly with the canvas renderer', function(done) { + map = createMap('canvas'); + var color; + function createSource(overlaps) { + color = '#3399CC'; + source = new ol.source.Vector({ + overlaps: overlaps + }); + addPolygon(720); + addPolygon(600); + addCircle(500); + addPolygon(250); + addCircle(200); + addPolygon(100); + return source; + } + function alternateColor() { + if (color == '#3399CC') { + color = '#CC9933'; + } else { + color = '#3399CC'; + } + return color; + } + var layer = new ol.layer.Vector({ + source: createSource(true), + style: function(feature) { + alternateColor(); + return new ol.style.Style({ + stroke: new ol.style.Stroke({ + color: alternateColor(), + width: 1.25 + }), + fill: new ol.style.Fill({ + color: alternateColor() + }) + }); + } + }); + map.addLayer(layer); + map.once('postrender', function() { + var canvas = map.getRenderer().canvas_; + // take a snapshot of this `overlaps: true` image + var referenceImage = canvas.getContext('2d').getImageData(0, 0, canvas.width, canvas.height); + // now render the same with `overlaps: false` + layer.setSource(createSource(false)); + // result should be exactly the same as with `overlaps: true` + map.once('postrender', function() { + expectResemble(map, referenceImage, 0, done); + }); + }); + }); + + it('interrupts stroke batches correctly with the canvas renderer', function(done) { + map = createMap('canvas'); + var color; + function createSource(overlaps) { + color = '#3399CC'; + source = new ol.source.Vector({ + overlaps: overlaps + }); + addLineString(720); + addLineString(600); + addLineString(250); + addLineString(100); + return source; + } + function alternateColor() { + if (color == '#3399CC') { + color = '#CC9933'; + } else { + color = '#3399CC'; + } + return color; + } + var layer = new ol.layer.Vector({ + source: createSource(true), + style: function(feature) { + alternateColor(); + return new ol.style.Style({ + stroke: new ol.style.Stroke({ + color: alternateColor(), + width: 1.25 + }), + fill: new ol.style.Fill({ + color: alternateColor() + }) + }); + } + }); + map.addLayer(layer); + map.once('postrender', function() { + var canvas = map.getRenderer().canvas_; + // take a snapshot of this `overlaps: true` image + var referenceImage = canvas.getContext('2d').getImageData(0, 0, canvas.width, canvas.height); + // now render the same with `overlaps: false` + layer.setSource(createSource(false)); + // result should be exactly the same as with `overlaps: true` + map.once('postrender', function() { + expectResemble(map, referenceImage, 0, done); + }); + }); + }); }); });