diff --git a/src/ol/render/canvas/imagereplay.js b/src/ol/render/canvas/imagereplay.js index 86ef562ce2..3d14617a3a 100644 --- a/src/ol/render/canvas/imagereplay.js +++ b/src/ol/render/canvas/imagereplay.js @@ -109,7 +109,7 @@ ol.inherits(ol.render.canvas.ImageReplay, ol.render.canvas.Replay); */ ol.render.canvas.ImageReplay.prototype.drawCoordinates_ = function(flatCoordinates, offset, end, stride) { return this.appendFlatCoordinates( - flatCoordinates, offset, end, stride, false); + flatCoordinates, offset, end, stride, false, false); }; diff --git a/src/ol/render/canvas/linestringreplay.js b/src/ol/render/canvas/linestringreplay.js index b0aa86c0fa..83ccbc483b 100644 --- a/src/ol/render/canvas/linestringreplay.js +++ b/src/ol/render/canvas/linestringreplay.js @@ -75,7 +75,7 @@ ol.inherits(ol.render.canvas.LineStringReplay, ol.render.canvas.Replay); ol.render.canvas.LineStringReplay.prototype.drawFlatCoordinates_ = function(flatCoordinates, offset, end, stride) { var myBegin = this.coordinates.length; var myEnd = this.appendFlatCoordinates( - flatCoordinates, offset, end, stride, false); + flatCoordinates, offset, end, stride, false, false); var moveToLineToInstruction = [ol.render.canvas.Instruction.MOVE_TO_LINE_TO, myBegin, myEnd]; this.instructions.push(moveToLineToInstruction); diff --git a/src/ol/render/canvas/polygonreplay.js b/src/ol/render/canvas/polygonreplay.js index d60ea7938c..5d93591df8 100644 --- a/src/ol/render/canvas/polygonreplay.js +++ b/src/ol/render/canvas/polygonreplay.js @@ -80,9 +80,6 @@ ol.render.canvas.PolygonReplay.prototype.drawFlatCoordinatess_ = function(flatCo var fill = state.fillStyle !== undefined; var stroke = state.strokeStyle != undefined; var numEnds = ends.length; - if (!fill && !stroke) { - return ends[numEnds - 1]; - } var beginPathInstruction = [ol.render.canvas.Instruction.BEGIN_PATH]; this.instructions.push(beginPathInstruction); this.hitDetectionInstructions.push(beginPathInstruction); @@ -90,7 +87,7 @@ ol.render.canvas.PolygonReplay.prototype.drawFlatCoordinatess_ = function(flatCo var end = ends[i]; var myBegin = this.coordinates.length; var myEnd = this.appendFlatCoordinates( - flatCoordinates, offset, end, stride, true); + flatCoordinates, offset, end, stride, true, !stroke); var moveToLineToInstruction = [ol.render.canvas.Instruction.MOVE_TO_LINE_TO, myBegin, myEnd]; this.instructions.push(moveToLineToInstruction); @@ -151,7 +148,7 @@ ol.render.canvas.PolygonReplay.prototype.drawCircle = function(circleGeometry, f var stride = circleGeometry.getStride(); var myBegin = this.coordinates.length; this.appendFlatCoordinates( - flatCoordinates, 0, flatCoordinates.length, stride, false); + flatCoordinates, 0, flatCoordinates.length, stride, false, false); var beginPathInstruction = [ol.render.canvas.Instruction.BEGIN_PATH]; var circleInstruction = [ol.render.canvas.Instruction.CIRCLE, myBegin]; this.instructions.push(beginPathInstruction, circleInstruction); @@ -178,11 +175,9 @@ ol.render.canvas.PolygonReplay.prototype.drawCircle = function(circleGeometry, f ol.render.canvas.PolygonReplay.prototype.drawPolygon = function(polygonGeometry, feature) { var state = this.state_; ol.DEBUG && console.assert(state, 'state should not be null'); - var fillStyle = state.fillStyle; var strokeStyle = state.strokeStyle; - if (fillStyle === undefined && strokeStyle === undefined) { - return; - } + ol.DEBUG && console.assert(state.fillStyle === undefined && strokeStyle === undefined, + 'fillStyle or strokeStyle should be defined'); if (strokeStyle !== undefined) { ol.DEBUG && console.assert(state.lineWidth !== undefined, 'state.lineWidth should be defined'); diff --git a/src/ol/render/canvas/replay.js b/src/ol/render/canvas/replay.js index 8302579e8c..2136448a2b 100644 --- a/src/ol/render/canvas/replay.js +++ b/src/ol/render/canvas/replay.js @@ -120,14 +120,18 @@ ol.inherits(ol.render.canvas.Replay, ol.render.VectorContext); * @param {number} offset Offset. * @param {number} end End. * @param {number} stride Stride. - * @param {boolean} close Close. + * @param {boolean} closed Last input coordinate equals first. + * @param {boolean} skipFirst Skip first coordinate. * @protected * @return {number} My end. */ -ol.render.canvas.Replay.prototype.appendFlatCoordinates = function(flatCoordinates, offset, end, stride, close) { +ol.render.canvas.Replay.prototype.appendFlatCoordinates = function(flatCoordinates, offset, end, stride, closed, skipFirst) { var myEnd = this.coordinates.length; var extent = this.getBufferedMaxExtent(); + if (skipFirst) { + offset += stride; + } var lastCoord = [flatCoordinates[offset], flatCoordinates[offset + 1]]; var nextCoord = [NaN, NaN]; var skipped = true; @@ -157,16 +161,11 @@ ol.render.canvas.Replay.prototype.appendFlatCoordinates = function(flatCoordinat lastRel = nextRel; } - // handle case where there is only one point to append - if (i === offset + stride) { + // Last coordinate equals first or only one point to append: + if ((closed && skipped) || i === offset + stride) { this.coordinates[myEnd++] = lastCoord[0]; this.coordinates[myEnd++] = lastCoord[1]; } - - if (close) { - this.coordinates[myEnd++] = flatCoordinates[offset]; - this.coordinates[myEnd++] = flatCoordinates[offset + 1]; - } return myEnd; }; diff --git a/src/ol/render/canvas/textreplay.js b/src/ol/render/canvas/textreplay.js index 7453382063..ba2794f6b4 100644 --- a/src/ol/render/canvas/textreplay.js +++ b/src/ol/render/canvas/textreplay.js @@ -115,7 +115,7 @@ ol.render.canvas.TextReplay.prototype.drawText = function(flatCoordinates, offse this.beginGeometry(geometry, feature); var myBegin = this.coordinates.length; var myEnd = - this.appendFlatCoordinates(flatCoordinates, offset, end, stride, false); + this.appendFlatCoordinates(flatCoordinates, offset, end, stride, false, false); var fill = !!this.textFillState_; var stroke = !!this.textStrokeState_; var drawTextInstruction = [ diff --git a/test/spec/ol/renderer/canvas/replay.test.js b/test/spec/ol/renderer/canvas/replay.test.js index 6c867d076f..68ee494d1c 100644 --- a/test/spec/ol/renderer/canvas/replay.test.js +++ b/test/spec/ol/renderer/canvas/replay.test.js @@ -165,13 +165,25 @@ describe('ol.render.canvas.Replay', function() { it('appends coordinates that are within the max extent', function() { var flat = [-110, 45, 110, 45, 110, -45, -110, -45]; - replay.appendFlatCoordinates(flat, 0, flat.length, 2, false); + replay.appendFlatCoordinates(flat, 0, flat.length, 2, false, false); expect(replay.coordinates).to.eql(flat); }); + it('appends polygon coordinates that are within the max extent', function() { + var flat = [-110, 45, 110, 45, 110, -45, -110, -45, -110, 45]; + replay.appendFlatCoordinates(flat, 0, flat.length, 2, true, false); + expect(replay.coordinates).to.eql(flat); + }); + + it('appends polygon coordinates that are within the max extent (skipping first)', function() { + var flat = [-110, 45, 110, 45, 110, -45, -110, -45, -110, 45]; + replay.appendFlatCoordinates(flat, 0, flat.length, 2, true, true); + expect(replay.coordinates).to.eql([110, 45, 110, -45, -110, -45, -110, 45]); + }); + it('works with a single coordinate (inside)', function() { var flat = [-110, 45]; - replay.appendFlatCoordinates(flat, 0, flat.length, 2, false); + replay.appendFlatCoordinates(flat, 0, flat.length, 2, false, false); expect(replay.coordinates).to.eql(flat); }); @@ -179,21 +191,35 @@ describe('ol.render.canvas.Replay', function() { // this could be changed, but to make the code simpler for properly // closing rings, we always add the first point var flat = [-110, 145]; - replay.appendFlatCoordinates(flat, 0, flat.length, 2, false); + replay.appendFlatCoordinates(flat, 0, flat.length, 2, false, false); expect(replay.coordinates).to.eql(flat); }); + it('always appends first polygon vertex (even if outside)', function() { + // this could be changed, but to make the code simpler for properly + // closing rings, we always add the first point + var flat = [-110, 145, -110, 145]; + replay.appendFlatCoordinates(flat, 0, flat.length, 2, true, false); + expect(replay.coordinates).to.eql(flat); + }); + + it('skips first polygon vertex upon request (also when outside)', function() { + var flat = [-110, 145, -110, 145]; + replay.appendFlatCoordinates(flat, 0, flat.length, 2, true, true); + expect(replay.coordinates).to.eql([-110, 145]); + }); + it('appends points when segments cross (top to bottom)', function() { // this means we get a few extra points when coordinates are not // part of a linestring or ring, but only a few extra var flat = [0, 200, 0, -200]; - replay.appendFlatCoordinates(flat, 0, flat.length, 2, false); + replay.appendFlatCoordinates(flat, 0, flat.length, 2, false, false); expect(replay.coordinates).to.eql(flat); }); it('appends points when segments cross (top to inside)', function() { var flat = [0, 200, 0, 0]; - replay.appendFlatCoordinates(flat, 0, flat.length, 2, false); + replay.appendFlatCoordinates(flat, 0, flat.length, 2, false, false); expect(replay.coordinates).to.eql(flat); }); @@ -201,22 +227,78 @@ describe('ol.render.canvas.Replay', function() { // this could be changed, but to make the code simpler for properly // closing rings, we always add the first segment var flat = [-10, 200, 10, 200]; - replay.appendFlatCoordinates(flat, 0, flat.length, 2, false); + replay.appendFlatCoordinates(flat, 0, flat.length, 2, false, false); expect(replay.coordinates).to.eql(flat); }); + it('always appends the first polygon segment (even when outside)', function() { + // this could be changed, but to make the code simpler for properly + // closing rings, we always add the first segment + var flat = [-10, 200, 10, 200, -10, 200]; + replay.appendFlatCoordinates(flat, 0, flat.length, 2, true, false); + expect(replay.coordinates).to.eql(flat); + }); + + it('skips first polygon segment upon request (also when outside)', function() { + var flat = [-10, 200, 10, 200, -10, 200]; + replay.appendFlatCoordinates(flat, 0, flat.length, 2, true, true); + expect(replay.coordinates).to.eql([10, 200, -10, 200]); + }); + + it('eliminates segments outside (and not changing rel)', function() { + var flat = [0, 0, 0, 200, 5, 200, 10, 200]; + replay.appendFlatCoordinates(flat, 0, flat.length, 2, false, false); + expect(replay.coordinates).to.eql([0, 0, 0, 200]); + }); + + it('eliminates polygon segments outside (and not changing rel)', function() { + var flat = [0, 0, 0, 200, 5, 200, 10, 200, 0, 0]; + replay.appendFlatCoordinates(flat, 0, flat.length, 2, true, false); + expect(replay.coordinates).to.eql([0, 0, 0, 200, 10, 200, 0, 0]); + }); + + it('eliminates polygon segments outside (skipping first and not changing rel)', function() { + var flat = [0, 0, 0, 10, 0, 200, 5, 200, 10, 200, 0, 0]; + replay.appendFlatCoordinates(flat, 0, flat.length, 2, true, true); + expect(replay.coordinates).to.eql([0, 10, 0, 200, 10, 200, 0, 0]); + }); + it('eliminates segments outside (and not changing rel)', function() { var flat = [0, 0, 0, 200, 10, 200]; - replay.appendFlatCoordinates(flat, 0, flat.length, 2, false); + replay.appendFlatCoordinates(flat, 0, flat.length, 2, false, false); expect(replay.coordinates).to.eql([0, 0, 0, 200]); }); + it('includes polygon segments outside (and not changing rel) when on last segment', function() { + var flat = [0, 0, 0, 200, 10, 200, 0, 0]; + replay.appendFlatCoordinates(flat, 0, flat.length, 2, true, false); + expect(replay.coordinates).to.eql(flat); + }); + + it('includes polygon segments outside (skipping first and not changing rel) when on last segment', function() { + var flat = [0, 0, 0, 200, 10, 200, 0, 0]; + replay.appendFlatCoordinates(flat, 0, flat.length, 2, true, true); + expect(replay.coordinates).to.eql([0, 200, 10, 200, 0, 0]); + }); + it('includes outside segments that change relationship', function() { var flat = [0, 0, 0, 200, 200, 200, 250, 200]; - replay.appendFlatCoordinates(flat, 0, flat.length, 2, false); + replay.appendFlatCoordinates(flat, 0, flat.length, 2, false, false); expect(replay.coordinates).to.eql([0, 0, 0, 200, 200, 200]); }); + it('includes outside polygon segments that change relationship when on last segment', function() { + var flat = [0, 0, 0, 200, 200, 200, 250, 200, 0, 0]; + replay.appendFlatCoordinates(flat, 0, flat.length, 2, true, false); + expect(replay.coordinates).to.eql(flat); + }); + + it('includes outside polygon segments that change relationship when on last segment (when skipping first)', function() { + var flat = [0, 0, 0, 200, 200, 200, 250, 200, 0, 0]; + replay.appendFlatCoordinates(flat, 0, flat.length, 2, true, true); + expect(replay.coordinates).to.eql([0, 200, 200, 200, 250, 200, 0, 0]); + }); + }); }); diff --git a/test_rendering/spec/ol/layer/expected/vector-canvas-simplified-fill.png b/test_rendering/spec/ol/layer/expected/vector-canvas-simplified-fill.png new file mode 100644 index 0000000000..47956d7011 Binary files /dev/null and b/test_rendering/spec/ol/layer/expected/vector-canvas-simplified-fill.png differ diff --git a/test_rendering/spec/ol/layer/expected/vector-canvas-simplified-stroke.png b/test_rendering/spec/ol/layer/expected/vector-canvas-simplified-stroke.png new file mode 100644 index 0000000000..035e1537d2 Binary files /dev/null and b/test_rendering/spec/ol/layer/expected/vector-canvas-simplified-stroke.png differ diff --git a/test_rendering/spec/ol/layer/expected/vector-canvas-simplified.png b/test_rendering/spec/ol/layer/expected/vector-canvas-simplified.png new file mode 100644 index 0000000000..26c028bcda Binary files /dev/null and b/test_rendering/spec/ol/layer/expected/vector-canvas-simplified.png differ diff --git a/test_rendering/spec/ol/layer/vector.test.js b/test_rendering/spec/ol/layer/vector.test.js index 0ab45d9762..563d231fb4 100644 --- a/test_rendering/spec/ol/layer/vector.test.js +++ b/test_rendering/spec/ol/layer/vector.test.js @@ -271,4 +271,91 @@ describe('ol.rendering.layer.Vector', function() { }); }); + describe('Polygon simplification', function() { + + var layer, map; + + beforeEach(function() { + var src = new ol.source.Vector({ + features: [ + new ol.Feature(new ol.geom.Polygon([[ + [-22, 58], + [-22, 78], + [-9, 78], + [-9, 58], + [-22, 58] + ]])), + new ol.Feature(new ol.geom.Polygon([[ + [-9, 58], + [-9, 78], + [4, 78], + [4, 58], + [-9, 58] + ]])) + ] + }); + layer = new ol.layer.Vector({ + renderBuffer: 0, + source: src + }); + var view = new ol.View({ + center: [-9.5, 78], + zoom: 2, + projection: 'EPSG:4326' + }); + + map = new ol.Map({ + layers: [layer], + target: createMapDiv(100, 100), + view: view + }); + }); + + afterEach(function() { + disposeMap(map); + }); + + it('renders partially out-of-view polygons with a fill and stroke', function(done) { + layer.setStyle(new ol.style.Style({ + stroke: new ol.style.Stroke({ + color: [0, 0, 0, 1], + width: 2 + }), + fill: new ol.style.Fill({ + color: [255, 0, 0, 1] + }) + })); + map.once('postrender', function() { + expectResemble(map, 'spec/ol/layer/expected/vector-canvas-simplified.png', + IMAGE_TOLERANCE, done); + }); + }); + + it('renders partially out-of-view polygons with a fill', function(done) { + layer.setStyle(new ol.style.Style({ + fill: new ol.style.Fill({ + color: [0, 0, 0, 1] + }) + })); + map.once('postrender', function() { + expectResemble(map, 'spec/ol/layer/expected/vector-canvas-simplified-fill.png', + IMAGE_TOLERANCE, done); + }); + }); + + it('renders partially out-of-view polygons with a stroke', function(done) { + layer.setStyle(new ol.style.Style({ + stroke: new ol.style.Stroke({ + color: [0, 0, 0, 1], + width: 2 + }) + })); + map.once('postrender', function() { + expectResemble(map, 'spec/ol/layer/expected/vector-canvas-simplified-stroke.png', + IMAGE_TOLERANCE, done); + }); + }); + + }); + });