From 22d03cb2eee6614587a206c60c7808853f03ce09 Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Sat, 19 Mar 2016 16:12:17 -0600 Subject: [PATCH 1/2] Avoid duplicate lineTo and correctly close rings --- src/ol/render/canvas/canvasimmediate.js | 11 +- test/spec/ol/render/canvasimmediate.test.js | 188 ++++++++++++-------- 2 files changed, 118 insertions(+), 81 deletions(-) diff --git a/src/ol/render/canvas/canvasimmediate.js b/src/ol/render/canvas/canvasimmediate.js index 5f40249903..68f2dad395 100644 --- a/src/ol/render/canvas/canvasimmediate.js +++ b/src/ol/render/canvas/canvasimmediate.js @@ -364,12 +364,15 @@ ol.render.canvas.Immediate.prototype.moveToLineTo_ = function(flatCoordinates, o flatCoordinates, offset, end, stride, this.transform_, this.pixelCoordinates_); context.moveTo(pixelCoordinates[0], pixelCoordinates[1]); - var i; - for (i = 2; i < pixelCoordinates.length; i += 2) { + var length = pixelCoordinates.length; + if (close) { + length -= 2; + } + for (var i = 2; i < length; i += 2) { context.lineTo(pixelCoordinates[i], pixelCoordinates[i + 1]); } if (close) { - context.lineTo(pixelCoordinates[0], pixelCoordinates[1]); + context.closePath(); } return end; }; @@ -384,12 +387,10 @@ ol.render.canvas.Immediate.prototype.moveToLineTo_ = function(flatCoordinates, o * @return {number} End. */ ol.render.canvas.Immediate.prototype.drawRings_ = function(flatCoordinates, offset, ends, stride) { - var context = this.context_; var i, ii; for (i = 0, ii = ends.length; i < ii; ++i) { offset = this.moveToLineTo_( flatCoordinates, offset, ends[i], stride, true); - context.closePath(); // FIXME is this needed here? } return offset; }; diff --git a/test/spec/ol/render/canvasimmediate.test.js b/test/spec/ol/render/canvasimmediate.test.js index 9575bde38c..5b78c31d16 100644 --- a/test/spec/ol/render/canvasimmediate.test.js +++ b/test/spec/ol/render/canvasimmediate.test.js @@ -148,32 +148,69 @@ describe('ol.render.canvas.Immediate', function() { }); - describe('#drawMultiPolygon', function() { + describe('#drawMultiPolygon()', function() { + it('creates the correct canvas instructions for 3D geometries', function() { - var log = { - lineTo: [], - moveTo: [] - }; - // FIXME move the canvas/context mocks outside of here for reuse - var context = { - setLineDash: function() {}, - beginPath: function() {}, - closePath: function() {}, - stroke: function() {}, - lineTo: function(x, y) { - log.lineTo.push([x, y]); - }, - moveTo: function(x, y) { - log.moveTo.push([x, y]); + + var instructions = []; + + function serialize(index, instruction) { + if (!instruction) { + return 'id: ' + index + ' NO INSTRUCTION'; } + var parts = [ + 'id: ' + index, + 'type: ' + instruction.type + ]; + if (instruction.args) { + parts.push('args: [' + instruction.args.map(function(arg) { + if (typeof arg === 'number') { + return arg.toFixed(9) + } else { + return arg; + } + }).join(', ') + ']'); + } + return parts.join(', '); + } + + var context = { + beginPath: function() {}, + moveTo: function(x, y) { + instructions.push({ + type: 'moveTo', + args: [x, y] + }); + }, + lineTo: function(x, y) { + instructions.push({ + type: 'lineTo', + args: [x, y] + }); + }, + closePath: function() { + instructions.push({ + type: 'closePath' + }); + }, + setLineDash: function() {}, + stroke: function() {} }; - var transform = [0.0004088332670837288, 0, 0, 0, 0, - -0.0004088332670837288, 0, 0, 0, 0, 1, 0, 4480.991370439071, - 1529.5752568707105, 0, 1]; - var extent = [-10960437.252092224, 2762924.0275091752, - -7572748.158493212, 3741317.9895594316]; - var canvas = new ol.render.canvas.Immediate(context, 1, extent, - transform); + + var transform = [ + 0.0004088332670837288, 0, 0, 0, + 0, -0.0004088332670837288, 0, 0, + 0, 0, 1, 0, + 4480.991370439071, 1529.5752568707105, 0, 1 + ]; + + var extent = [ + -10960437.252092224, 2762924.0275091752, + -7572748.158493212, 3741317.9895594316 + ]; + + var canvas = new ol.render.canvas.Immediate(context, 1, extent, transform); + canvas.strokeState_ = { lineCap: 'round', lineDash: [], @@ -182,60 +219,59 @@ describe('ol.render.canvas.Immediate', function() { miterLimit: 10, strokeStyle: '#00FFFF' }; - var multiPolygonGeometry = new ol.geom.MultiPolygon([ - [[[-80.736061, 28.788576000000006, 0], - [-80.763557, 28.821799999999996, 0], - [-80.817406, 28.895123999999996, 0], - [-80.891304, 29.013130000000004, 0], - [-80.916512, 29.071560000000005, 0], - [-80.899323, 29.061249000000004, 0], - [-80.862663, 28.991361999999995, 0], - [-80.736061, 28.788576000000006, 0]]], [[ - [-82.102127, 26.585724, 0], - [-82.067139, 26.497208, 0], - [-82.097641, 26.493585999999993, 0], - [-82.135895, 26.642279000000002, 0], - [-82.183495, 26.683082999999996, 0], - [-82.128838, 26.693342, 0], - [-82.102127, 26.585724, 0]]] - ]).transform('EPSG:4326', 'EPSG:3857'); + + var multiPolygonGeometry = new ol.geom.MultiPolygon([[[ + // first polygon + [-80.736061, 28.788576000000006, 0], // moveTo() + [-80.763557, 28.821799999999996, 0], // lineTo() + [-80.817406, 28.895123999999996, 0], // lineTo() + [-80.891304, 29.013130000000004, 0], // lineTo() + [-80.916512, 29.071560000000005, 0], // lineTo() + [-80.899323, 29.061249000000004, 0], // lineTo() + [-80.862663, 28.991361999999995, 0], // lineTo() + [-80.736061, 28.788576000000006, 0] // closePath() + ]],[[ + // second polygon + [-82.102127, 26.585724, 0], // moveTo() + [-82.067139, 26.497208, 0], // lineTo() + [-82.097641, 26.493585999999993, 0], // lineTo() + [-82.135895, 26.642279000000002, 0], // lineTo() + [-82.183495, 26.683082999999996, 0], // lineTo() + [-82.128838, 26.693342, 0], // lineTo() + [-82.102127, 26.585724, 0] // closePath() + ]]]).transform('EPSG:4326', 'EPSG:3857'); + canvas.drawMultiPolygon(multiPolygonGeometry, null); - expect(log.lineTo.length).to.be(15); - expect(log.lineTo[0][0]).to.roughlyEqual(805.3521540835154, 1e-9); - expect(log.lineTo[0][1]).to.roughlyEqual(158.76358389011807, 1e-9); - expect(log.lineTo[1][0]).to.roughlyEqual(802.9014262612932, 1e-9); - expect(log.lineTo[1][1]).to.roughlyEqual(154.95335187132082, 1e-9); - expect(log.lineTo[2][0]).to.roughlyEqual(799.5382461724039, 1e-9); - expect(log.lineTo[2][1]).to.roughlyEqual(148.815592819916, 1e-9); - expect(log.lineTo[3][0]).to.roughlyEqual(798.3910020835165, 1e-9); - expect(log.lineTo[3][1]).to.roughlyEqual(145.77392230456553, 1e-9); - expect(log.lineTo[4][0]).to.roughlyEqual(799.1732925724045, 1e-9); - expect(log.lineTo[4][1]).to.roughlyEqual(146.31080369865776, 1e-9); - expect(log.lineTo[5][0]).to.roughlyEqual(800.8417299057378, 1e-9); - expect(log.lineTo[5][1]).to.roughlyEqual(149.94832216046188, 1e-9); - expect(log.lineTo[6][0]).to.roughlyEqual(806.6035275946265, 1e-9); - expect(log.lineTo[6][1]).to.roughlyEqual(160.48916296287916, 1e-9); - expect(log.lineTo[7][0]).to.roughlyEqual(806.6035275946265, 1e-9); - expect(log.lineTo[7][1]).to.roughlyEqual(160.48916296287916, 1e-9); - expect(log.lineTo[8][0]).to.roughlyEqual(746.0246888390716, 1e-9); - expect(log.lineTo[8][1]).to.roughlyEqual(278.22094795365365, 1e-9); - expect(log.lineTo[9][0]).to.roughlyEqual(744.6365089279602, 1e-9); - expect(log.lineTo[9][1]).to.roughlyEqual(278.40513424671826, 1e-9); - expect(log.lineTo[10][0]).to.roughlyEqual(742.8955268835157, 1e-9); - expect(log.lineTo[10][1]).to.roughlyEqual(270.83899948444764, 1e-9); - expect(log.lineTo[11][0]).to.roughlyEqual(740.7291979946272, 1e-9); - expect(log.lineTo[11][1]).to.roughlyEqual(268.76099731369345, 1e-9); - expect(log.lineTo[12][0]).to.roughlyEqual(743.2166987946266, 1e-9); - expect(log.lineTo[12][1]).to.roughlyEqual(268.23842607400616, 1e-9); - expect(log.lineTo[13][0]).to.roughlyEqual(744.4323460835158, 1e-9); - expect(log.lineTo[13][1]).to.roughlyEqual(273.7179168205373, 1e-9); - expect(log.lineTo[14][0]).to.roughlyEqual(744.4323460835158, 1e-9); - expect(log.lineTo[14][1]).to.roughlyEqual(273.7179168205373, 1e-9); - expect(log.moveTo.length).to.be(2); - expect(log.moveTo[0][0]).to.roughlyEqual(806.6035275946265, 1e-9); - expect(log.moveTo[0][1]).to.roughlyEqual(160.48916296287916, 1e-9); - expect(log.moveTo[1][0]).to.roughlyEqual(744.4323460835158, 1e-9); - expect(log.moveTo[1][1]).to.roughlyEqual(273.7179168205373, 1e-9); + + var expected = [ + // first polygon + {type: 'moveTo', args: [806.6035275946265, 160.48916296287916]}, + {type: 'lineTo', args: [805.3521540835154, 158.76358389011807]}, + {type: 'lineTo', args: [802.9014262612932, 154.95335187132082]}, + {type: 'lineTo', args: [799.5382461724039, 148.815592819916]}, + {type: 'lineTo', args: [798.3910020835165, 145.77392230456553]}, + {type: 'lineTo', args: [799.1732925724045, 146.31080369865776]}, + {type: 'lineTo', args: [800.8417299057378, 149.94832216046188]}, + {type: 'closePath'}, + // second polygon + {type: 'moveTo', args: [744.4323460835158, 273.7179168205373]}, + {type: 'lineTo', args: [746.0246888390716, 278.22094795365365]}, + {type: 'lineTo', args: [744.6365089279602, 278.40513424671826]}, + {type: 'lineTo', args: [742.8955268835157, 270.83899948444764]}, + {type: 'lineTo', args: [740.7291979946272, 268.76099731369345]}, + {type: 'lineTo', args: [743.2166987946266, 268.23842607400616]}, + {type: 'closePath'} + ]; + + + for (var i = 0, ii = instructions.length; i < ii; ++i) { + var actualInstruction = serialize(i, instructions[i]); + var expectedInstruction = serialize(i, expected[i]); + expect(actualInstruction).to.equal(expectedInstruction); + } + + expect(instructions.length).to.equal(expected.length); + }); }); }); From cd70f58e3456bd05273d2cc94b101cd5ed4e6472 Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Sat, 19 Mar 2016 16:45:26 -0600 Subject: [PATCH 2/2] Rendering tests for vector context --- .../ol/expected/render-linestring-bevel.png | Bin 0 -> 950 bytes .../ol/expected/render-linestring-butt.png | Bin 0 -> 873 bytes .../spec/ol/expected/render-linestring.png | Bin 0 -> 966 bytes .../spec/ol/expected/render-point.png | Bin 0 -> 717 bytes .../spec/ol/expected/render-polygon.png | Bin 0 -> 792 bytes test_rendering/spec/ol/render.test.js | 139 ++++++++++++++++++ 6 files changed, 139 insertions(+) create mode 100644 test_rendering/spec/ol/expected/render-linestring-bevel.png create mode 100644 test_rendering/spec/ol/expected/render-linestring-butt.png create mode 100644 test_rendering/spec/ol/expected/render-linestring.png create mode 100644 test_rendering/spec/ol/expected/render-point.png create mode 100644 test_rendering/spec/ol/expected/render-polygon.png create mode 100644 test_rendering/spec/ol/render.test.js diff --git a/test_rendering/spec/ol/expected/render-linestring-bevel.png b/test_rendering/spec/ol/expected/render-linestring-bevel.png new file mode 100644 index 0000000000000000000000000000000000000000..596ae5328f1b452fb010972c998040c0adcb532b GIT binary patch literal 950 zcmeAS@N?(olHy`uVBq!ia0vp^DIm179z7I#qw zQ3uY2ObeL3JggeLoY(`HWI-ZJm@YM1dasdDj<&bHHSyV#o_U`ZRGS*le|IX%TDB-c z^+M#zP6?Kdo4x`Hj7ifPX3p{PXqcgzpr$S?q>#orvIyT!+X6F4`;-Gc*8)H7((ZKmU9GPXt8tZRfp3x{QbNW+RPv zzh*II=4`VRDX%?RV633D`RsxnuV43?F)Equmg?HQ@ACQgs~dAtrnNm zcD1@=x=&YAklxCZe=Mj5*arqjBHG>{ymfOB?Jg51M8#IHRY^R4cx| zlI;n@9@Y_c8mA)E(|#=@bdQ!#`(t@rhKft%)pTwmQs~*((``*yN8A!-%I$Nkhjr^ z&hERKb~ND5m%HT$x(yXlb*r~ExfyR((&+p3Zr9}WZv{@*bRy55HEVmcD!=GtlKd;9 z&AkgOUeB&sG5z^*eFlB)X}UXaP7Rpz^)BNMslPX(d&2ig}Rr9{AJU)BYsr;UjD)@v_k*uE_BEIuCF z|LRZLetyNTOL}}198Nq4No8E*HSLYGLQ27^O$>pm-rslyj4VPU8&*tG{msT9IcJrQ zL&(WVwahI%exYItt5QzxV{~F$uEi}7x+gJN^=RMrly$W8eT6ArFC#pE(o=cYMr cjQy@}-SfW+BDZhfn zPebbP38w`0#?}Z99Y%>h=_JJsED08Q9jyn18XoRY6l3ycXnWj7T}hwp6Oq3ZUd~>! zev;jL%RiI81Yhc({G~kAOk|&?)45CHp7&L2tvLQ}Uvhl%mpx8go%*pSBCBqsx@@|) z#Ktww;+3h%z7WN!r{j#~skNS%uC}D`W@gZe8cipw>AusQo=NMaT-cNu^rFVOl_#!j zhlthvZ66F2Jx}vZc6z>B$I8Xy)26#X+4c3U@5PKVZ>DLM?0fw^IMzu`KU%l-T>0y> zxtk}ro^D%u;b-4F6BEwJJvo6~bKl>~ou8#RA(bB}?DhS@s+I%OYL#2hg-;6nYR9tc zP4|?)li!Nje^jnHY;t7+i)8;72H_u0L0wKpDKY<^O)PjL__l&muW`M`({&Rh9XO4v zmh9owX?(wKinF4zqfpvb4@(}c#`gu+d=-ru^-ijuSu-(c&lHspcjs6-dlcS08Wi^N zXlo85ua)wl(|VIOb*+Cl$y8&`t5o*0y>Y9;K5qRR@SbOR<;`K})S4?8hEY-xA9{Cbbg64S@O4z#L; zKMhiS(l=$*!mUnB>dr5Ls#ZM@cHN@_kv*sgO`G+imc4$&bgK~+vC=+-M;$diT$4rD|`R?^k(y3 zxd*FS33lL66bru z5YuOHtz_;_|-Pj)7mq*;0~kqy>chZDqJAX(rRP$=*sJ=!SvD ztA8sTP9(kzxbk=D0TTts#7rQ|*HP1<>F~R@MepAKQ2&#s(0eEk$j+~`-^%Urc*9ko zin+UY-sM>md;`ROI?p7B>5^rRVArnwOOM-Kb#OV9ws6I7ZQJJ6OiE(kSb)MpJ*$2T zT-?wfaLoRaa^2C~ugsT2g@qU%d+j~IP``SCNTbVv8TTq#GK`l!-nfII-c_U9AxeVF z;+UL<@cN4A%FtqI@NnqFElg?c%^traBTK%nl==e*X#>! Xa+R>2{dEwIkn zs|GG7%>c#=j3ESw(?rmeA_cmK|td-rr7 zIhby6SZe6Q!nB)%MM)rnd4q-2Kv5G;@@{#PK9S#(Z}OirTH%>HXa0%*ck^YI)TK?c zp9D`@obtD__>S7oiBFVt{}#^-V}BC)k$OO1EYr&np=KPRQ0 zi?6*?Z?S-HLgKNfVzRQ~220r{Y)p&UUofkxD>B}j??twks6zhiRdS1d&YN~wSEu^B z^J<2d;or}-nB`sGWd6|ReHoYCgnj2FE?-@9ak=gryQNGMa>YOUPIz&2FVlo+Paf;o zPSUx2>3pY*XhR0at4oEvjt?agk9}StSsqy~SuOSEfqx`($8UivswV4R)veyut6J{) z?e5C+O0PGsiS+feIe&Y(-j}uS{`f362<@k literal 0 HcmV?d00001 diff --git a/test_rendering/spec/ol/expected/render-polygon.png b/test_rendering/spec/ol/expected/render-polygon.png new file mode 100644 index 0000000000000000000000000000000000000000..864ecfbf9208ae160626e8b28416fe4166c4015c GIT binary patch literal 792 zcmeAS@N?(olHy`uVBq!ia0vp^DImzha3}fpZ!gl&ExMq?aGxk_f`C=mdvVpb*=yT zY}c4DKAo^E0p*4nyi5%YN6s)BTc&V0oRKk@?A%5MSJ*hEUjOChCEF*(&9W`;pTq8X zf63B6eEZcHEf4&#j9DArET85ulXXeYJZt^`ZL4p3p8kDt?K!4R^14?x?7e^KQ`Q!} zh7!wNJm2#xnvXxf9q-)c!Q^qhLayG};aK&ptB*c2e%gAvea~#>xeO&Fyiw8X)(oKfXo53lQ2jhEIA*UAsv)ZVc5--!uNcsl#s z8JH$4RQ;Ks9BW9vcT z@(1@6gq&eIl%BM=LBzB@|9hQft;f%!t-oaQZ_EB~x_700ifrwjwq>5G+0y*)R|qMj vg)lG)7_*;wGC`5Su=s!vpClPv8^-5LpQVca{&EPI;u$<${an^LB{Ts5CcjlN literal 0 HcmV?d00001 diff --git a/test_rendering/spec/ol/render.test.js b/test_rendering/spec/ol/render.test.js new file mode 100644 index 0000000000..6f15943f0f --- /dev/null +++ b/test_rendering/spec/ol/render.test.js @@ -0,0 +1,139 @@ +goog.provide('ol.test.rendering.render'); + +describe('ol.render', function() { + + var context; + + beforeEach(function() { + context = document.createElement('canvas').getContext('2d'); + }); + + describe('ol.render.toContext()', function() { + + it('creates a vector context from a Canvas 2d context', function() { + var vectorContext = ol.render.toContext(context, {size: [100, 100]}); + expect(vectorContext).to.be.a(ol.render.VectorContext); + expect(vectorContext).to.be.a(ol.render.canvas.Immediate); + }); + + it('can be used to render a point geometry', function(done) { + var vectorContext = ol.render.toContext(context, {size: [100, 100]}); + + var style = new ol.style.Style({ + image: new ol.style.Circle({ + fill: new ol.style.Fill({ + color: 'green' + }), + radius: 10 + }) + }); + + vectorContext.setStyle(style); + vectorContext.drawGeometry(new ol.geom.Point([50, 50])); + + resembleCanvas(context.canvas, + 'spec/ol/expected/render-point.png', IMAGE_TOLERANCE, done); + + }); + + it('can be used to render a linestring geometry', function(done) { + var vectorContext = ol.render.toContext(context, {size: [100, 100]}); + + var style = new ol.style.Style({ + stroke: new ol.style.Stroke({ + color: 'red', + width: 14 + }) + }); + + vectorContext.setStyle(style); + vectorContext.drawGeometry(new ol.geom.LineString([ + [10, 60], [30, 40], [50, 60], [70, 40], [90, 60] + ])); + + resembleCanvas(context.canvas, + 'spec/ol/expected/render-linestring.png', IMAGE_TOLERANCE, done); + + }); + + it('respects lineCap for linestring', function(done) { + var vectorContext = ol.render.toContext(context, {size: [100, 100]}); + + var style = new ol.style.Style({ + stroke: new ol.style.Stroke({ + lineCap: 'butt', + color: 'red', + width: 14 + }) + }); + + vectorContext.setStyle(style); + vectorContext.drawGeometry(new ol.geom.LineString([ + [10, 60], [30, 40], [50, 60], [70, 40], [90, 60] + ])); + + resembleCanvas(context.canvas, + 'spec/ol/expected/render-linestring-butt.png', IMAGE_TOLERANCE, done); + + }); + + it('respects lineJoin for linestring', function(done) { + var vectorContext = ol.render.toContext(context, {size: [100, 100]}); + + var style = new ol.style.Style({ + stroke: new ol.style.Stroke({ + lineJoin: 'bevel', + color: 'red', + width: 14 + }) + }); + + vectorContext.setStyle(style); + vectorContext.drawGeometry(new ol.geom.LineString([ + [10, 60], [30, 40], [50, 60], [70, 40], [90, 60] + ])); + + resembleCanvas(context.canvas, + 'spec/ol/expected/render-linestring-bevel.png', IMAGE_TOLERANCE, done); + + }); + + it('can be used to render a polygon geometry', function(done) { + var vectorContext = ol.render.toContext(context, {size: [100, 100]}); + + var style = new ol.style.Style({ + stroke: new ol.style.Stroke({ + color: 'blue', + width: 8 + }), + fill: new ol.style.Fill({ + color: 'rgba(0,0,255,0.5)' + }) + }); + + vectorContext.setStyle(style); + + vectorContext.drawGeometry(new ol.geom.Polygon([ + [[25, 25], [75, 25], [75, 75], [25, 75], [25, 25]], + [[40, 40], [40, 60], [60, 60], [60, 40], [40, 40]] + ])); + + resembleCanvas(context.canvas, + 'spec/ol/expected/render-polygon.png', IMAGE_TOLERANCE, done); + + }); + + }); + +}); + +goog.require('ol.geom.LineString'); +goog.require('ol.geom.Point'); +goog.require('ol.geom.Polygon'); +goog.require('ol.render'); +goog.require('ol.render.VectorContext'); +goog.require('ol.render.canvas.Immediate'); +goog.require('ol.style.Circle'); +goog.require('ol.style.Fill'); +goog.require('ol.style.Stroke'); +goog.require('ol.style.Style');