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 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'); 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/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..00ab10d11e --- /dev/null +++ b/src/ol/geom/circle.js @@ -0,0 +1,197 @@ +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 d = Math.sqrt(dx * dx + dy * dy); + var distance = Math.max(d, 0); + var squaredDistance = distance * distance; + if (squaredDistance < minSquaredDistance) { + 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; + } +}; + + +/** + * @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/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 }; diff --git a/src/ol/render/canvas/canvasreplay.js b/src/ol/render/canvas/canvasreplay.js index 693cc9f974..5628b84b9e 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; @@ -379,6 +392,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 */ @@ -997,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 */ 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. 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_ }; 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 */ 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'); 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');