From 3647f13e2ee20ebf8b79883a6997215e2a7cba6a Mon Sep 17 00:00:00 2001 From: Tom Payne Date: Sun, 12 Jan 2014 20:37:31 +0100 Subject: [PATCH 01/11] Add ol.geom.Circle --- src/ol/geom/circle.exports | 11 ++ src/ol/geom/circle.js | 192 +++++++++++++++++++++++++++++++ src/ol/geom/geometry.js | 3 +- test/spec/ol/geom/circle.test.js | 192 +++++++++++++++++++++++++++++++ 4 files changed, 397 insertions(+), 1 deletion(-) create mode 100644 src/ol/geom/circle.exports create mode 100644 src/ol/geom/circle.js create mode 100644 test/spec/ol/geom/circle.test.js diff --git a/src/ol/geom/circle.exports b/src/ol/geom/circle.exports new file mode 100644 index 0000000000..e08d6f83be --- /dev/null +++ b/src/ol/geom/circle.exports @@ -0,0 +1,11 @@ +@exportSymbol ol.geom.Circle +@exportProperty ol.geom.Circle.prototype.clone +@exportProperty ol.geom.Circle.prototype.getCenter +@exportProperty ol.geom.Circle.prototype.getExtent +@exportProperty ol.geom.Circle.prototype.getRadius +@exportProperty ol.geom.Circle.prototype.getSimplifiedGeometry +@exportProperty ol.geom.Circle.prototype.getType +@exportProperty ol.geom.Circle.prototype.setCenter +@exportProperty ol.geom.Circle.prototype.setCenterAndRadius +@exportProperty ol.geom.Circle.prototype.setRadius +@exportProperty ol.geom.Circle.prototype.transform diff --git a/src/ol/geom/circle.js b/src/ol/geom/circle.js new file mode 100644 index 0000000000..ea3a3343d8 --- /dev/null +++ b/src/ol/geom/circle.js @@ -0,0 +1,192 @@ +goog.provide('ol.geom.Circle'); + +goog.require('goog.asserts'); +goog.require('ol.extent'); +goog.require('ol.geom.GeometryType'); +goog.require('ol.geom.SimpleGeometry'); +goog.require('ol.geom.flat'); + + + +/** + * @constructor + * @extends {ol.geom.SimpleGeometry} + * @param {ol.geom.RawPoint} center Center. + * @param {number=} opt_radius Radius. + * @param {ol.geom.GeometryLayout=} opt_layout Layout. + */ +ol.geom.Circle = function(center, opt_radius, opt_layout) { + goog.base(this); + var radius = goog.isDef(opt_radius) ? opt_radius : 0; + this.setCenterAndRadius(center, radius, opt_layout); +}; +goog.inherits(ol.geom.Circle, ol.geom.SimpleGeometry); + + +/** + * @inheritDoc + */ +ol.geom.Circle.prototype.clone = function() { + var circle = new ol.geom.Circle(null); + circle.setFlatCoordinates(this.layout, this.flatCoordinates.slice()); + return circle; +}; + + +/** + * @inheritDoc + */ +ol.geom.Circle.prototype.closestPointXY = + function(x, y, closestPoint, minSquaredDistance) { + var flatCoordinates = this.flatCoordinates; + var radius = flatCoordinates[this.stride] - flatCoordinates[0]; + var dx = x - flatCoordinates[0]; + var dy = y - flatCoordinates[1]; + var distance = Math.max(Math.sqrt(dx * dx + dy * dy) - radius, 0); + var squaredDistance = distance * distance; + if (squaredDistance < minSquaredDistance) { + // FIXME it must be possible to do this without trigonometric functions + var theta = Math.atan2(dy, dx); + closestPoint[0] = flatCoordinates[0] + radius * Math.cos(theta); + closestPoint[1] = flatCoordinates[1] + radius * Math.sin(theta); + return squaredDistance; + } else { + return minSquaredDistance; + } +}; + + +/** + * @inheritDoc + */ +ol.geom.Circle.prototype.containsXY = function(x, y) { + var flatCoordinates = this.flatCoordinates; + var dx = x - flatCoordinates[0]; + var dy = y - flatCoordinates[1]; + var r = flatCoordinates[this.stride] - flatCoordinates[0]; + return dx * dx + dy * dy <= r; +}; + + +/** + * @return {ol.geom.RawPoint} Center. + */ +ol.geom.Circle.prototype.getCenter = function() { + return this.flatCoordinates.slice(0, this.stride); +}; + + +/** + * @inheritDoc + */ +ol.geom.Circle.prototype.getExtent = function(opt_extent) { + if (this.extentRevision != this.revision) { + var flatCoordinates = this.flatCoordinates; + var radius = flatCoordinates[this.stride] - flatCoordinates[0]; + this.extent = ol.extent.createOrUpdate( + flatCoordinates[0] - radius, flatCoordinates[1] - radius, + flatCoordinates[0] + radius, flatCoordinates[1] + radius, + this.extent); + this.extentRevision = this.revision; + } + goog.asserts.assert(goog.isDef(this.extent)); + return ol.extent.returnOrUpdate(this.extent, opt_extent); +}; + + +/** + * @return {number} Radius. + */ +ol.geom.Circle.prototype.getRadius = function() { + var dx = this.flatCoordinates[this.stride] - this.flatCoordinates[0]; + var dy = this.flatCoordinates[this.stride + 1] - this.flatCoordinates[1]; + return Math.sqrt(dx * dx + dy * dy); +}; + + +/** + * @inheritDoc + */ +ol.geom.Circle.prototype.getSimplifiedGeometry = function(squaredTolerance) { + return this; +}; + + +/** + * @inheritDoc + */ +ol.geom.Circle.prototype.getType = function() { + return ol.geom.GeometryType.CIRCLE; +}; + + +/** + * @param {ol.geom.RawPoint} center Center. + */ +ol.geom.Circle.prototype.setCenter = function(center) { + var stride = this.stride; + goog.asserts.assert(center.length == stride); + var radius = this.flatCoordinates[stride] - this.flatCoordinates[0]; + var flatCoordinates = center.slice(); + flatCoordinates[stride] = flatCoordinates[0] + radius; + var i; + for (i = 1; i < stride; ++i) { + flatCoordinates[stride + i] = center[i]; + } + this.setFlatCoordinates(this.layout, flatCoordinates); +}; + + +/** + * @param {ol.geom.RawPoint} center Center. + * @param {number} radius Radius. + * @param {ol.geom.GeometryLayout=} opt_layout Layout. + */ +ol.geom.Circle.prototype.setCenterAndRadius = + function(center, radius, opt_layout) { + if (goog.isNull(center)) { + this.setFlatCoordinates(ol.geom.GeometryLayout.XY, null); + } else { + this.setLayout(opt_layout, center, 0); + if (goog.isNull(this.flatCoordinates)) { + this.flatCoordinates = []; + } + var flatCoordinates = this.flatCoordinates; + var offset = ol.geom.flat.deflateCoordinate( + flatCoordinates, 0, center, this.stride); + flatCoordinates[offset++] = flatCoordinates[0] + radius; + var i, ii; + for (i = 1, ii = this.stride; i < ii; ++i) { + flatCoordinates[offset++] = flatCoordinates[i]; + } + flatCoordinates.length = offset; + this.dispatchChangeEvent(); + } +}; + + +/** + * @param {ol.geom.GeometryLayout} layout Layout. + * @param {Array.} flatCoordinates Flat coordinates. + */ +ol.geom.Circle.prototype.setFlatCoordinates = + function(layout, flatCoordinates) { + this.setFlatCoordinatesInternal(layout, flatCoordinates); + this.dispatchChangeEvent(); +}; + + +/** + * @param {number} radius Radius. + */ +ol.geom.Circle.prototype.setRadius = function(radius) { + goog.asserts.assert(!goog.isNull(this.flatCoordinates)); + this.flatCoordinates[this.stride] = this.flatCoordinates[0] + radius; + this.dispatchChangeEvent(); +}; + + +/** + * @inheritDoc + */ +ol.geom.Circle.prototype.transform = goog.abstractMethod; diff --git a/src/ol/geom/geometry.js b/src/ol/geom/geometry.js index bb93809b62..7a41e08503 100644 --- a/src/ol/geom/geometry.js +++ b/src/ol/geom/geometry.js @@ -18,7 +18,8 @@ ol.geom.GeometryType = { MULTI_POINT: 'MultiPoint', MULTI_LINE_STRING: 'MultiLineString', MULTI_POLYGON: 'MultiPolygon', - GEOMETRY_COLLECTION: 'GeometryCollection' + GEOMETRY_COLLECTION: 'GeometryCollection', + CIRCLE: 'Circle' }; diff --git a/test/spec/ol/geom/circle.test.js b/test/spec/ol/geom/circle.test.js new file mode 100644 index 0000000000..e7e6fa119d --- /dev/null +++ b/test/spec/ol/geom/circle.test.js @@ -0,0 +1,192 @@ +goog.provide('ol.test.geom.Circle'); + + +describe('ol.geom.Circle', function() { + + describe('with a unit circle', function() { + + var circle; + beforeEach(function() { + circle = new ol.geom.Circle([0, 0], 1); + }); + + describe('#clone', function() { + + it('returns a clone', function() { + var clone = circle.clone(); + expect(clone).to.be.an(ol.geom.Circle); + expect(clone.getCenter()).to.eql(circle.getCenter()); + expect(clone.getCenter()).not.to.be(circle.getCenter()); + expect(clone.getRadius()).to.be(circle.getRadius()); + }); + + }); + + describe('#containsCoordinate', function() { + + it('contains the center', function() { + expect(circle.containsCoordinate([0, 0])).to.be(true); + }); + + it('contains points inside the perimeter', function() { + expect(circle.containsCoordinate([0.5, 0.5])).to.be(true); + expect(circle.containsCoordinate([-0.5, 0.5])).to.be(true); + expect(circle.containsCoordinate([-0.5, -0.5])).to.be(true); + expect(circle.containsCoordinate([0.5, -0.5])).to.be(true); + }); + + it('contains points on the perimeter', function() { + expect(circle.containsCoordinate([1, 0])).to.be(true); + expect(circle.containsCoordinate([0, 1])).to.be(true); + expect(circle.containsCoordinate([-1, 0])).to.be(true); + expect(circle.containsCoordinate([0, -1])).to.be(true); + }); + + it('does not contain points outside the perimeter', function() { + expect(circle.containsCoordinate([2, 0])).to.be(false); + expect(circle.containsCoordinate([1, 1])).to.be(false); + expect(circle.containsCoordinate([-2, 0])).to.be(false); + expect(circle.containsCoordinate([0, -2])).to.be(false); + }); + + }); + + describe('#getCenter', function() { + + it('returns the expected value', function() { + expect(circle.getCenter()).to.eql([0, 0]); + }); + + }); + + describe('#getClosestPoint', function() { + + it('returns the closest point on the perimeter', function() { + var closestPoint; + closestPoint = circle.getClosestPoint([2, 0]); + expect(closestPoint[0]).to.roughlyEqual(1, 1e-15); + expect(closestPoint[1]).to.roughlyEqual(0, 1e-15); + closestPoint = circle.getClosestPoint([2, 2]); + expect(closestPoint[0]).to.roughlyEqual(Math.sqrt(0.5), 1e-15); + expect(closestPoint[1]).to.roughlyEqual(Math.sqrt(0.5), 1e-15); + closestPoint = circle.getClosestPoint([0, 2]); + expect(closestPoint[0]).to.roughlyEqual(0, 1e-15); + expect(closestPoint[1]).to.roughlyEqual(1, 1e-15); + closestPoint = circle.getClosestPoint([-2, 2]); + expect(closestPoint[0]).to.roughlyEqual(-Math.sqrt(0.5), 1e-15); + expect(closestPoint[1]).to.roughlyEqual(Math.sqrt(0.5), 1e-15); + closestPoint = circle.getClosestPoint([-2, 0]); + expect(closestPoint[0]).to.roughlyEqual(-1, 1e-15); + expect(closestPoint[1]).to.roughlyEqual(0, 1e-15); + closestPoint = circle.getClosestPoint([-2, -2]); + expect(closestPoint[0]).to.roughlyEqual(-Math.sqrt(0.5), 1e-15); + expect(closestPoint[1]).to.roughlyEqual(-Math.sqrt(0.5), 1e-15); + closestPoint = circle.getClosestPoint([0, -2]); + expect(closestPoint[0]).to.roughlyEqual(0, 1e-15); + expect(closestPoint[1]).to.roughlyEqual(-1, 1e-15); + closestPoint = circle.getClosestPoint([2, -2]); + expect(closestPoint[0]).to.roughlyEqual(Math.sqrt(0.5), 1e-15); + expect(closestPoint[1]).to.roughlyEqual(-Math.sqrt(0.5), 1e-15); + }); + + }); + + describe('#getExtent', function() { + + it('returns the expected value', function() { + expect(circle.getExtent()).to.eql([-1, -1, 1, 1]); + }); + + }); + + describe('#getRadius', function() { + + it('returns the expected value', function() { + expect(circle.getRadius()).to.be(1); + }); + + }); + + describe('#getSimplifiedGeometry', function() { + + it('returns the same geometry', function() { + expect(circle.getSimplifiedGeometry(1)).to.be(circle); + }); + + }); + + describe('#getType', function() { + + it('returns the expected value', function() { + expect(circle.getType()).to.be(ol.geom.GeometryType.CIRCLE); + }); + + }); + + describe('#setCenter', function() { + + it('sets the center', function() { + circle.setCenter([1, 2]); + expect(circle.getCenter()).to.eql([1, 2]); + }); + + it('fires a change event', function() { + var spy = sinon.spy(); + circle.on('change', spy); + circle.setCenter([1, 2]); + expect(spy.calledOnce).to.be(true); + }); + + }); + + describe('#setFlatCoordinates', function() { + + it('sets both center and radius', function() { + circle.setFlatCoordinates(ol.geom.GeometryLayout.XY, [1, 2, 4, 2]); + expect(circle.getCenter()).to.eql([1, 2]); + expect(circle.getRadius()).to.be(3); + }); + + it('fires a single change event', function() { + var spy = sinon.spy(); + circle.on('change', spy); + circle.setFlatCoordinates(ol.geom.GeometryLayout.XY, [1, 2, 4, 2]); + expect(spy.calledOnce).to.be(true); + }); + + }); + + describe('#setRadius', function() { + + it('sets the radius', function() { + circle.setRadius(2); + expect(circle.getRadius()).to.be(2); + }); + + it('fires a change event', function() { + var spy = sinon.spy(); + circle.on('change', spy); + circle.setRadius(2); + expect(spy.calledOnce).to.be(true); + }); + + }); + + describe('#transform', function() { + + it('throws an exception', function() { + expect(function() { + circle.transform(ol.proj.identityTransform); + }).to.throwException(); + }); + + }); + + }); + +}); + + +goog.require('ol.geom.Circle'); +goog.require('ol.geom.GeometryType'); +goog.require('ol.proj'); From b2b74a5ee6e53695798cdf4895307095482ea1b1 Mon Sep 17 00:00:00 2001 From: Tom Payne Date: Sun, 12 Jan 2014 20:38:06 +0100 Subject: [PATCH 02/11] Add ol.render.IRender#drawCircleGeometry --- src/ol/render/canvas/canvasreplay.js | 6 ++++++ src/ol/render/irender.js | 9 +++++++++ 2 files changed, 15 insertions(+) diff --git a/src/ol/render/canvas/canvasreplay.js b/src/ol/render/canvas/canvasreplay.js index 25d263dbc2..cd354b1848 100644 --- a/src/ol/render/canvas/canvasreplay.js +++ b/src/ol/render/canvas/canvasreplay.js @@ -379,6 +379,12 @@ ol.render.canvas.Replay.prototype.reverseHitDetectionInstructions_ = ol.render.canvas.Replay.prototype.drawAsync = goog.abstractMethod; +/** + * @inheritDoc + */ +ol.render.canvas.Replay.prototype.drawCircleGeometry = goog.abstractMethod; + + /** * @inheritDoc */ diff --git a/src/ol/render/irender.js b/src/ol/render/irender.js index 8536e8318e..f65eea30fe 100644 --- a/src/ol/render/irender.js +++ b/src/ol/render/irender.js @@ -19,6 +19,15 @@ ol.render.IRender.prototype.drawAsync = function(zIndex, callback) { }; +/** + * @param {ol.geom.Circle} circleGeometry Circle geometry. + * @param {Object} data Opaque data object, + */ +ol.render.IRender.prototype.drawCircleGeometry = + function(circleGeometry, data) { +}; + + /** * @param {ol.Feature} feature Feature. * @param {ol.style.Style} style Style. From 5df47634f38a09480c326ccd221bfe11c316e7a2 Mon Sep 17 00:00:00 2001 From: Tom Payne Date: Sun, 12 Jan 2014 20:38:40 +0100 Subject: [PATCH 03/11] Add ol.render.webgl.Immediate#drawCircleGeometry --- src/ol/render/webgl/webglimmediate.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/ol/render/webgl/webglimmediate.js b/src/ol/render/webgl/webglimmediate.js index 70308b7964..d6f7aac533 100644 --- a/src/ol/render/webgl/webglimmediate.js +++ b/src/ol/render/webgl/webglimmediate.js @@ -20,6 +20,14 @@ ol.render.webgl.Immediate.prototype.drawAsync = function(zIndex, callback) { }; +/** + * @inheritDoc + */ +ol.render.webgl.Immediate.prototype.drawCircleGeometry = + function(circleGeometry, data) { +}; + + /** * @inheritDoc */ From a8f5dff9cf4841f5fffdfa8be518ab9b3655f26d Mon Sep 17 00:00:00 2001 From: Tom Payne Date: Sun, 12 Jan 2014 20:39:29 +0100 Subject: [PATCH 04/11] Add ol.render.canvas.Immediate#drawCircleGeometry --- src/ol/render/canvas/canvasimmediate.js | 36 ++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/src/ol/render/canvas/canvasimmediate.js b/src/ol/render/canvas/canvasimmediate.js index e0493fd144..ec8c7deeef 100644 --- a/src/ol/render/canvas/canvasimmediate.js +++ b/src/ol/render/canvas/canvasimmediate.js @@ -263,6 +263,39 @@ ol.render.canvas.Immediate.prototype.drawAsync = function(zIndex, callback) { }; +/** + * @inheritDoc + */ +ol.render.canvas.Immediate.prototype.drawCircleGeometry = + function(circleGeometry, data) { + /* + if (!ol.extent.intersects(this.extent_, circleGeometry.getExtent())) { + return; + } + var state = this.state_; + if (!goog.isDef(state.fillStyle) && !goog.isDef(state.strokeStyle)) { + return; + } + this.setFillStrokeStyles_(); + var context = this.context_; + var pixelCoordinates = ol.geom.transformSimpleGeometry2D( + circleGeometry, this.transform_, this.pixelCoordinates_); + var dx = pixelCoordinates[2] - pixelCoordinates[0]; + var dy = pixelCoordinates[3] - pixelCoordinates[1]; + var radius = Math.sqrt(dx * dx + dy * dy); + context.beginPath(); + context.arc(pixelCoordinates[0], pixelCoordinates[1], radius, 0, 2 * Math.PI); + if (goog.isDef(state.fillStyle)) { + context.fill(); + } + if (goog.isDef(state.strokeStyle)) { + goog.asserts.assert(goog.isDef(state.lineWidth)); + context.stroke(); + } + */ +}; + + /** * @inheritDoc */ @@ -597,5 +630,6 @@ ol.render.canvas.Immediate.GEOMETRY_RENDERES_ = { ol.render.canvas.Immediate.prototype.drawMultiLineStringGeometry, 'MultiPolygon': ol.render.canvas.Immediate.prototype.drawMultiPolygonGeometry, 'GeometryCollection': - ol.render.canvas.Immediate.prototype.drawGeometryCollectionGeometry + ol.render.canvas.Immediate.prototype.drawGeometryCollectionGeometry, + 'Circle': ol.render.canvas.Immediate.prototype.drawCircleGeometry }; From 5052bef4a553c3db7eae311f2c9ea759474958e2 Mon Sep 17 00:00:00 2001 From: Tom Payne Date: Sun, 12 Jan 2014 20:40:15 +0100 Subject: [PATCH 05/11] Add ol.replay.canvas.Instruction.CIRCLE --- src/ol/render/canvas/canvasreplay.js | 31 ++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/src/ol/render/canvas/canvasreplay.js b/src/ol/render/canvas/canvasreplay.js index cd354b1848..e8df00fe62 100644 --- a/src/ol/render/canvas/canvasreplay.js +++ b/src/ol/render/canvas/canvasreplay.js @@ -27,14 +27,15 @@ goog.require('ol.vec.Mat4'); ol.render.canvas.Instruction = { BEGIN_GEOMETRY: 0, BEGIN_PATH: 1, - CLOSE_PATH: 2, - DRAW_IMAGE: 3, - END_GEOMETRY: 4, - FILL: 5, - MOVE_TO_LINE_TO: 6, - SET_FILL_STYLE: 7, - SET_STROKE_STYLE: 8, - STROKE: 9 + CIRCLE: 2, + CLOSE_PATH: 3, + DRAW_IMAGE: 4, + END_GEOMETRY: 5, + FILL: 6, + MOVE_TO_LINE_TO: 7, + SET_FILL_STYLE: 8, + SET_STROKE_STYLE: 9, + STROKE: 10 }; @@ -178,7 +179,7 @@ ol.render.canvas.Replay.prototype.replay_ = } var i = 0; // instruction index var ii = instructions.length; // end of instructions - var d; // data index + var d = 0; // data index var dd; // end of per-instruction data var localTransform = this.tmpLocalTransform_; while (i < ii) { @@ -198,6 +199,18 @@ ol.render.canvas.Replay.prototype.replay_ = context.beginPath(); ++i; break; + case ol.render.canvas.Instruction.CIRCLE: + var x1 = pixelCoordinates[d]; + var y1 = pixelCoordinates[d + 1]; + var x2 = pixelCoordinates[d + 2]; + var y2 = pixelCoordinates[d + 3]; + var dx = x2 - x1; + var dy = y2 - y1; + var r = Math.sqrt(dx * dx + dy * dy); + context.arc(x1, y1, r, 0, 2 * Math.PI, true); + d += 4; + ++i; + break; case ol.render.canvas.Instruction.CLOSE_PATH: context.closePath(); ++i; From 825c5290f6e21ae08cf9d8df252934bfd79e4fe9 Mon Sep 17 00:00:00 2001 From: Tom Payne Date: Sun, 12 Jan 2014 20:42:11 +0100 Subject: [PATCH 06/11] Add ol.render.canvas.PolygonReplay#drawCircleGeometry --- src/ol/render/canvas/canvasreplay.js | 51 ++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/src/ol/render/canvas/canvasreplay.js b/src/ol/render/canvas/canvasreplay.js index e8df00fe62..f1b347bebf 100644 --- a/src/ol/render/canvas/canvasreplay.js +++ b/src/ol/render/canvas/canvasreplay.js @@ -1016,6 +1016,57 @@ ol.render.canvas.PolygonReplay.prototype.drawFlatCoordinatess_ = }; +/** + * @inheritDoc + */ +ol.render.canvas.PolygonReplay.prototype.drawCircleGeometry = + function(circleGeometry, data) { + var state = this.state_; + goog.asserts.assert(!goog.isNull(state)); + var fillStyle = state.fillStyle; + var strokeStyle = state.strokeStyle; + if (!goog.isDef(fillStyle) && !goog.isDef(strokeStyle)) { + return; + } + if (goog.isDef(strokeStyle)) { + goog.asserts.assert(goog.isDef(state.lineWidth)); + } + ol.extent.extend(this.extent_, circleGeometry.getExtent()); + this.setFillStrokeStyles_(); + this.beginGeometry(circleGeometry); + // always fill the circle for hit detection + this.hitDetectionInstructions.push( + [ol.render.canvas.Instruction.SET_FILL_STYLE, + ol.color.asString(ol.render.canvas.defaultFillStyle)]); + if (goog.isDef(state.strokeStyle)) { + this.hitDetectionInstructions.push( + [ol.render.canvas.Instruction.SET_STROKE_STYLE, + state.strokeStyle, state.lineWidth, state.lineCap, state.lineJoin, + state.miterLimit, state.lineDash]); + } + var flatCoordinates = circleGeometry.getFlatCoordinates(); + var stride = circleGeometry.getStride(); + this.appendFlatCoordinates( + flatCoordinates, 0, flatCoordinates.length, stride, false); + var beginPathInstruction = [ol.render.canvas.Instruction.BEGIN_PATH]; + var circleInstruction = [ol.render.canvas.Instruction.CIRCLE]; + this.instructions.push(beginPathInstruction, circleInstruction); + this.hitDetectionInstructions.push(beginPathInstruction, circleInstruction); + this.endGeometry(circleGeometry, data); + var fillInstruction = [ol.render.canvas.Instruction.FILL]; + this.hitDetectionInstructions.push(fillInstruction); + if (goog.isDef(state.fillStyle)) { + this.instructions.push(fillInstruction); + } + if (goog.isDef(state.strokeStyle)) { + goog.asserts.assert(goog.isDef(state.lineWidth)); + var strokeInstruction = [ol.render.canvas.Instruction.STROKE]; + this.instructions.push(strokeInstruction); + this.hitDetectionInstructions.push(strokeInstruction); + } +}; + + /** * @inheritDoc */ From 8dde621e610967388f632042c25b19ac0e775cb2 Mon Sep 17 00:00:00 2001 From: Tom Payne Date: Sun, 12 Jan 2014 20:42:36 +0100 Subject: [PATCH 07/11] add ol.render.vector.renderCircleGeometry_ --- src/ol/render/vector.js | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/src/ol/render/vector.js b/src/ol/render/vector.js index 409a73f38a..4d5d46aab6 100644 --- a/src/ol/render/vector.js +++ b/src/ol/render/vector.js @@ -1,6 +1,7 @@ goog.provide('ol.renderer.vector'); goog.require('goog.asserts'); +goog.require('ol.geom.Circle'); goog.require('ol.geom.GeometryCollection'); goog.require('ol.geom.LineString'); goog.require('ol.geom.MultiLineString'); @@ -12,6 +13,29 @@ goog.require('ol.render.IReplayGroup'); goog.require('ol.style.Style'); +/** + * @param {ol.render.IReplayGroup} replayGroup Replay group. + * @param {ol.geom.Geometry} geometry Geometry. + * @param {ol.style.Style} style Style. + * @param {Object} data Opaque data object. + * @private + */ +ol.renderer.vector.renderCircleGeometry_ = + function(replayGroup, geometry, style, data) { + var fillStyle = style.getFill(); + var strokeStyle = style.getStroke(); + if (goog.isNull(fillStyle) && goog.isNull(strokeStyle)) { + return; + } + goog.asserts.assertInstanceof(geometry, ol.geom.Circle); + var circleGeometry = /** @type {ol.geom.Circle} */ (geometry); + var replay = replayGroup.getReplay( + style.getZIndex(), ol.render.ReplayType.POLYGON); + replay.setFillStrokeStyle(fillStyle, strokeStyle); + replay.drawCircleGeometry(circleGeometry, data); +}; + + /** * @param {ol.render.IReplayGroup} replayGroup Replay group. * @param {ol.Feature} feature Feature. @@ -196,5 +220,6 @@ ol.renderer.vector.GEOMETRY_RENDERERS_ = { 'MultiPoint': ol.renderer.vector.renderMultiPointGeometry_, 'MultiLineString': ol.renderer.vector.renderMultiLineStringGeometry_, 'MultiPolygon': ol.renderer.vector.renderMultiPolygonGeometry_, - 'GeometryCollection': ol.renderer.vector.renderGeometryCollectionGeometry_ + 'GeometryCollection': ol.renderer.vector.renderGeometryCollectionGeometry_, + 'Circle': ol.renderer.vector.renderCircleGeometry_ }; From f0d9a451e526d5bfcded390fae3decb2ca41d243 Mon Sep 17 00:00:00 2001 From: Tom Payne Date: Sun, 12 Jan 2014 20:44:32 +0100 Subject: [PATCH 08/11] Add an ol.geom.Circle to geojson example --- examples/geojson.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/examples/geojson.js b/examples/geojson.js index 19a9572977..13d7c0f70d 100644 --- a/examples/geojson.js +++ b/examples/geojson.js @@ -2,6 +2,7 @@ goog.require('ol.Feature'); goog.require('ol.Map'); goog.require('ol.RendererHint'); goog.require('ol.View2D'); +goog.require('ol.geom.Circle'); goog.require('ol.layer.Tile'); goog.require('ol.layer.Vector'); goog.require('ol.source.GeoJSON'); @@ -71,6 +72,15 @@ var styles = { color: 'magenta' }) }) + })], + 'Circle': [new ol.style.Style({ + stroke: new ol.style.Stroke({ + color: 'red', + width: 2 + }), + fill: new ol.style.Fill({ + color: 'rgba(255,0,0,0.2)' + }) })] }; @@ -164,6 +174,8 @@ var vectorSource = new ol.source.GeoJSON( } })); +vectorSource.addFeature(new ol.Feature(new ol.geom.Circle([5e6, 7e6], 1e6))); + var vectorLayer = new ol.layer.Vector({ source: vectorSource, styleFunction: styleFunction From 5886d0dec097abd6bda9283721d7e6bae31604b7 Mon Sep 17 00:00:00 2001 From: Tom Payne Date: Sun, 12 Jan 2014 21:54:13 +0100 Subject: [PATCH 09/11] Add FIXME to geolocation example --- examples/geolocation.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/examples/geolocation.js b/examples/geolocation.js index ceaee64199..0197b181bd 100644 --- a/examples/geolocation.js +++ b/examples/geolocation.js @@ -1,3 +1,7 @@ +// FIXME use an ol.geom.Circle to display a circle with accuracy +// FIXME this circle will need to compensate for the pointResolution of the +// FIXME EPSG:3857 projection + goog.require('ol.Geolocation'); goog.require('ol.Map'); goog.require('ol.Overlay'); From 99eca3037d7b5f7b5dd756cbdf585f86b1722e89 Mon Sep 17 00:00:00 2001 From: Tom Payne Date: Mon, 13 Jan 2014 12:45:45 +0100 Subject: [PATCH 10/11] Encode ol.geom.Circles as empty geometry collections in GeoJSON GeoJSON does not support circles, nor null geometries. Empty geometry collections seem to be the way to represent at null geometry in GeoJSON. --- src/ol/format/geojsonformat.js | 16 +++++++++++++++- test/spec/ol/format/geojsonformat.test.js | 11 +++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/src/ol/format/geojsonformat.js b/src/ol/format/geojsonformat.js index d703970609..b9f3b59886 100644 --- a/src/ol/format/geojsonformat.js +++ b/src/ol/format/geojsonformat.js @@ -153,6 +153,19 @@ ol.format.GeoJSON.writeGeometry_ = function(geometry) { }; +/** + * @param {ol.geom.Geometry} geometry Geometry. + * @private + * @return {GeoJSONGeometryCollection} Empty GeoJSON geometry collection. + */ +ol.format.GeoJSON.writeEmptyGeometryCollectionGeometry_ = function(geometry) { + return /** @type {GeoJSONGeometryCollection} */ ({ + 'type': 'GeometryCollection', + 'geometries': [] + }); +}; + + /** * @param {ol.geom.Geometry} geometry Geometry. * @private @@ -283,7 +296,8 @@ ol.format.GeoJSON.GEOMETRY_WRITERS_ = { 'MultiPoint': ol.format.GeoJSON.writeMultiPointGeometry_, 'MultiLineString': ol.format.GeoJSON.writeMultiLineStringGeometry_, 'MultiPolygon': ol.format.GeoJSON.writeMultiPolygonGeometry_, - 'GeometryCollection': ol.format.GeoJSON.writeGeometryCollectionGeometry_ + 'GeometryCollection': ol.format.GeoJSON.writeGeometryCollectionGeometry_, + 'Circle': ol.format.GeoJSON.writeEmptyGeometryCollectionGeometry_ }; diff --git a/test/spec/ol/format/geojsonformat.test.js b/test/spec/ol/format/geojsonformat.test.js index a6792d834f..d9248b6661 100644 --- a/test/spec/ol/format/geojsonformat.test.js +++ b/test/spec/ol/format/geojsonformat.test.js @@ -458,6 +458,16 @@ describe('ol.format.GeoJSON', function() { expect(geometries[i].getCoordinates()). to.eql(gotGeometries[i].getCoordinates()); } + + }); + + it('encodes a circle as an empty geometry collection', function() { + var circle = new ol.geom.Circle([0, 0], 1); + var geojson = format.writeGeometry(circle); + expect(geojson).to.eql({ + 'type': 'GeometryCollection', + 'geometries': [] + }); }); }); @@ -468,6 +478,7 @@ describe('ol.format.GeoJSON', function() { goog.require('ol.Feature'); goog.require('ol.extent'); goog.require('ol.format.GeoJSON'); +goog.require('ol.geom.Circle'); goog.require('ol.geom.GeometryCollection'); goog.require('ol.geom.LineString'); goog.require('ol.geom.LinearRing'); From da31e621990bee43ddf8fe7b6b0af024b6f9be52 Mon Sep 17 00:00:00 2001 From: Tom Payne Date: Mon, 13 Jan 2014 23:42:02 +0100 Subject: [PATCH 11/11] More efficient ol.geom.Circle#closestPointXY, thanks @tschaub --- src/ol/geom/circle.js | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/ol/geom/circle.js b/src/ol/geom/circle.js index ea3a3343d8..00ab10d11e 100644 --- a/src/ol/geom/circle.js +++ b/src/ol/geom/circle.js @@ -42,13 +42,18 @@ ol.geom.Circle.prototype.closestPointXY = var radius = flatCoordinates[this.stride] - flatCoordinates[0]; var dx = x - flatCoordinates[0]; var dy = y - flatCoordinates[1]; - var distance = Math.max(Math.sqrt(dx * dx + dy * dy) - radius, 0); + var d = Math.sqrt(dx * dx + dy * dy); + var distance = Math.max(d, 0); var squaredDistance = distance * distance; if (squaredDistance < minSquaredDistance) { - // FIXME it must be possible to do this without trigonometric functions - var theta = Math.atan2(dy, dx); - closestPoint[0] = flatCoordinates[0] + radius * Math.cos(theta); - closestPoint[1] = flatCoordinates[1] + radius * Math.sin(theta); + if (d === 0) { + closestPoint[0] = flatCoordinates[0]; + closestPoint[1] = flatCoordinates[1]; + } else { + var delta = radius / d; + closestPoint[0] = flatCoordinates[0] + delta * dx; + closestPoint[1] = flatCoordinates[1] + delta * dy; + } return squaredDistance; } else { return minSquaredDistance;