diff --git a/src/ol/style/circlestyle.js b/src/ol/style/circlestyle.js index 7983b0a280..dd5f63c8f2 100644 --- a/src/ol/style/circlestyle.js +++ b/src/ol/style/circlestyle.js @@ -1,5 +1,6 @@ goog.provide('ol.style.Circle'); +goog.require('goog.asserts'); goog.require('goog.dom'); goog.require('goog.dom.TagName'); goog.require('ol.color'); @@ -26,12 +27,17 @@ ol.style.Circle = function(opt_options) { var options = goog.isDef(opt_options) ? opt_options : {}; + /** + * @private + * @type {Array.|null} + */ + this.checksums_ = null; + /** * @private * @type {HTMLCanvasElement} */ - this.canvas_ = /** @type {HTMLCanvasElement} */ - (goog.dom.createElement(goog.dom.TagName.CANVAS)); + this.canvas_ = null; /** * @private @@ -47,9 +53,9 @@ ol.style.Circle = function(opt_options) { /** * @private - * @type {Array.} + * @type {ol.style.Stroke} */ - this.origin_ = [0, 0]; + this.stroke_ = goog.isDef(options.stroke) ? options.stroke : null; /** * @private @@ -59,29 +65,29 @@ ol.style.Circle = function(opt_options) { /** * @private - * @type {ol.style.Stroke} + * @type {Array.} */ - this.stroke_ = goog.isDef(options.stroke) ? options.stroke : null; - - var size = this.render_(); + this.origin_ = [0, 0]; /** * @private * @type {Array.} */ - this.anchor_ = [size / 2, size / 2]; + this.anchor_ = null; /** * @private * @type {ol.Size} */ - this.size_ = [size, size]; + this.size_ = null; /** * @private - * @type {Array.|null} + * @type {ol.Size} */ - this.checksums_ = null; + this.imageSize_ = null; + + this.render_(options.atlasManager); /** * @type {boolean} @@ -149,7 +155,7 @@ ol.style.Circle.prototype.getImageState = function() { * @inheritDoc */ ol.style.Circle.prototype.getImageSize = function() { - return this.size_; + return this.imageSize_; }; @@ -208,16 +214,20 @@ ol.style.Circle.prototype.unlistenImageChange = goog.nullFunction; /** - * @private - * @return {number} Size. + * @typedef {{strokeStyle: (string|undefined), strokeWidth: number, + * size: number}} */ -ol.style.Circle.prototype.render_ = function() { - var canvas = this.canvas_; - var strokeStyle, strokeWidth; +ol.style.Circle.RenderOptions; - if (goog.isNull(this.stroke_)) { - strokeWidth = 0; - } else { + +/** + * @private + * @param {ol.style.AtlasManager|undefined} atlasManager + */ +ol.style.Circle.prototype.render_ = function(atlasManager) { + var strokeStyle, strokeWidth = 0, imageSize; + + if (!goog.isNull(this.stroke_)) { strokeStyle = ol.color.asString(this.stroke_.getColor()); strokeWidth = this.stroke_.getWidth(); if (!goog.isDef(strokeWidth)) { @@ -227,54 +237,112 @@ ol.style.Circle.prototype.render_ = function() { var size = 2 * (this.radius_ + strokeWidth) + 1; - // draw the circle on the canvas + /** @type {ol.style.Circle.RenderOptions} */ + var renderOptions = { + strokeStyle: strokeStyle, + strokeWidth: strokeWidth, + size: size + }; - canvas.height = size; - canvas.width = size; + if (!goog.isDef(atlasManager)) { + // no atlas manager is used, create a new canvas + this.canvas_ = /** @type {HTMLCanvasElement} */ + (goog.dom.createElement(goog.dom.TagName.CANVAS)); + this.canvas_.height = size; + this.canvas_.width = size; - // canvas.width and height are rounded to the closest integer - size = canvas.width; + // canvas.width and height are rounded to the closest integer + size = this.canvas_.width; + imageSize = size; - var context = /** @type {CanvasRenderingContext2D} */ - (canvas.getContext('2d')); - context.arc(size / 2, size / 2, this.radius_, 0, 2 * Math.PI, true); + // draw the circle on the canvas + var context = /** @type {CanvasRenderingContext2D} */ + (this.canvas_.getContext('2d')); + this.draw_(renderOptions, context, 0, 0); + } else { + // an atlas manager is used, add the symbol to an atlas + size = Math.round(size); + + var id = this.getChecksum(); + var info = atlasManager.add( + id, size, size, goog.bind(this.draw_, this, renderOptions)); + goog.asserts.assert(info !== null, 'circle radius is too large'); + + this.canvas_ = info.image; + this.origin_ = [info.offsetX, info.offsetY]; + imageSize = info.image.width; + } + + this.anchor_ = [size / 2, size / 2]; + this.size_ = [size, size]; + this.imageSize_ = [imageSize, imageSize]; + + // deal with the hit detection canvas + if (!goog.isNull(this.fill_)) { + this.hitDetectionCanvas_ = this.canvas_; + } else { + this.createHitDetectionCanvas_(renderOptions); + } +}; + + +/** + * @private + * @param {ol.style.Circle.RenderOptions} renderOptions + * @param {CanvasRenderingContext2D} context + * @param {number} x The origin for the symbol (x). + * @param {number} y The origin for the symbol (y). + */ +ol.style.Circle.prototype.draw_ = function(renderOptions, context, x, y) { + // reset transform + context.setTransform(1, 0, 0, 1, 0, 0); + + // then move to (x, y) + context.translate(x, y); + + context.beginPath(); + context.arc( + renderOptions.size / 2, renderOptions.size / 2, + this.radius_, 0, 2 * Math.PI, true); if (!goog.isNull(this.fill_)) { context.fillStyle = ol.color.asString(this.fill_.getColor()); context.fill(); } if (!goog.isNull(this.stroke_)) { - context.strokeStyle = strokeStyle; - context.lineWidth = strokeWidth; + context.strokeStyle = renderOptions.strokeStyle; + context.lineWidth = renderOptions.strokeWidth; context.stroke(); } + context.closePath(); +}; - // deal with the hit detection canvas - if (!goog.isNull(this.fill_)) { - this.hitDetectionCanvas_ = canvas; - } else { - this.hitDetectionCanvas_ = /** @type {HTMLCanvasElement} */ - (goog.dom.createElement(goog.dom.TagName.CANVAS)); - canvas = this.hitDetectionCanvas_; +/** + * @private + * @param {ol.style.Circle.RenderOptions} renderOptions + */ +ol.style.Circle.prototype.createHitDetectionCanvas_ = function(renderOptions) { + this.hitDetectionCanvas_ = /** @type {HTMLCanvasElement} */ + (goog.dom.createElement(goog.dom.TagName.CANVAS)); + var canvas = this.hitDetectionCanvas_; - canvas.height = size; - canvas.width = size; + canvas.height = renderOptions.size; + canvas.width = renderOptions.size; - context = /** @type {CanvasRenderingContext2D} */ - (canvas.getContext('2d')); - context.arc(size / 2, size / 2, this.radius_, 0, 2 * Math.PI, true); + var context = /** @type {CanvasRenderingContext2D} */ + (canvas.getContext('2d')); + context.arc( + renderOptions.size / 2, renderOptions.size / 2, + this.radius_, 0, 2 * Math.PI, true); - context.fillStyle = ol.render.canvas.defaultFillStyle; - context.fill(); - if (!goog.isNull(this.stroke_)) { - context.strokeStyle = strokeStyle; - context.lineWidth = strokeWidth; - context.stroke(); - } + context.fillStyle = ol.render.canvas.defaultFillStyle; + context.fill(); + if (!goog.isNull(this.stroke_)) { + context.strokeStyle = renderOptions.strokeStyle; + context.lineWidth = renderOptions.strokeWidth; + context.stroke(); } - - return size; }; diff --git a/test/spec/ol/style/circlestyle.test.js b/test/spec/ol/style/circlestyle.test.js index 00eb1765a6..40c9f66b5a 100644 --- a/test/spec/ol/style/circlestyle.test.js +++ b/test/spec/ol/style/circlestyle.test.js @@ -3,6 +3,32 @@ goog.provide('ol.test.style.Circle'); describe('ol.style.Circle', function() { + describe('#constructor', function() { + + it('creates a canvas if no atlas is used', function() { + var style = new ol.style.Circle({radius: 10}); + expect(style.getImage()).to.be.an(HTMLCanvasElement); + expect(style.getSize()).to.eql([21, 21]); + expect(style.getImageSize()).to.eql([21, 21]); + expect(style.getOrigin()).to.eql([0, 0]); + expect(style.getAnchor()).to.eql([10.5, 10.5]); + expect(style.getImage()).to.not.be(style.getHitDetectionImage()); + expect(style.getHitDetectionImage()).to.be.an(HTMLCanvasElement); + }); + + it('adds itself to an atlas manager', function() { + var atlasManager = new ol.style.AtlasManager({size: 512}); + var style = new ol.style.Circle({radius: 10, atlasManager: atlasManager}); + expect(style.getImage()).to.be.an(HTMLCanvasElement); + expect(style.getSize()).to.eql([21, 21]); + expect(style.getImageSize()).to.eql([512, 512]); + expect(style.getOrigin()).to.eql([1, 1]); + expect(style.getAnchor()).to.eql([10.5, 10.5]); + expect(style.getImage()).to.not.be(style.getHitDetectionImage()); + expect(style.getHitDetectionImage()).to.be.an(HTMLCanvasElement); + }); + }); + describe('#getChecksum', function() { it('calculates the same hash code for default options', function() { @@ -162,6 +188,7 @@ describe('ol.style.Circle', function() { }); }); +goog.require('ol.style.AtlasManager'); goog.require('ol.style.Circle'); goog.require('ol.style.Fill'); goog.require('ol.style.Stroke');