Merge pull request #14 from tsauerwein/webgl-point-atlas-manager
[webgl-point] Add atlas manager
This commit is contained in:
@@ -6227,3 +6227,36 @@ olx.ViewState.prototype.resolution;
|
||||
* @api
|
||||
*/
|
||||
olx.ViewState.prototype.rotation;
|
||||
|
||||
|
||||
/**
|
||||
* @typedef {{size: (number|undefined),
|
||||
* maxSize: (number|undefined),
|
||||
* space: (number|undefined)}}
|
||||
* @api
|
||||
*/
|
||||
olx.style.AtlasManagerOptions;
|
||||
|
||||
|
||||
/**
|
||||
* The size in pixels of the first atlas image (default: 256).
|
||||
* @type {number|undefined}
|
||||
* @api
|
||||
*/
|
||||
olx.style.AtlasManagerOptions.prototype.size;
|
||||
|
||||
|
||||
/**
|
||||
* The maximum size in pixels of atlas images (default: 2048).
|
||||
* @type {number|undefined}
|
||||
* @api
|
||||
*/
|
||||
olx.style.AtlasManagerOptions.prototype.maxSize;
|
||||
|
||||
|
||||
/**
|
||||
* The space in pixels between images (default: 1).
|
||||
* @type {number|undefined}
|
||||
* @api
|
||||
*/
|
||||
olx.style.AtlasManagerOptions.prototype.space;
|
||||
|
||||
@@ -113,27 +113,42 @@ ol.has.POINTER = 'PointerEvent' in goog.global;
|
||||
ol.has.MSPOINTER = !!(goog.global.navigator.msPointerEnabled);
|
||||
|
||||
|
||||
/**
|
||||
* The maximum supported WebGL texture size in pixels. If WebGL is not
|
||||
* supported, the value is set to `-1`.
|
||||
* @const
|
||||
* @type {number}
|
||||
*/
|
||||
ol.has.WEBGL_MAX_TEXTURE_SIZE;
|
||||
|
||||
|
||||
/**
|
||||
* True if browser supports WebGL.
|
||||
* @const
|
||||
* @type {boolean}
|
||||
* @api stable
|
||||
*/
|
||||
ol.has.WEBGL = ol.ENABLE_WEBGL && (
|
||||
/**
|
||||
* @return {boolean} WebGL supported.
|
||||
*/
|
||||
function() {
|
||||
if (!('WebGLRenderingContext' in goog.global)) {
|
||||
return false;
|
||||
}
|
||||
ol.has.WEBGL;
|
||||
|
||||
|
||||
(function() {
|
||||
if (ol.ENABLE_WEBGL) {
|
||||
var hasWebGL = false, textureSize = -1;
|
||||
if ('WebGLRenderingContext' in goog.global) {
|
||||
try {
|
||||
var canvas = /** @type {HTMLCanvasElement} */
|
||||
(goog.dom.createElement(goog.dom.TagName.CANVAS));
|
||||
return !goog.isNull(ol.webgl.getContext(canvas, {
|
||||
var gl = ol.webgl.getContext(canvas, {
|
||||
failIfMajorPerformanceCaveat: true
|
||||
}));
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
})();
|
||||
});
|
||||
if (!goog.isNull(gl)) {
|
||||
hasWebGL = true;
|
||||
textureSize = /** @type {number} */
|
||||
(gl.getParameter(gl.MAX_TEXTURE_SIZE));
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
ol.has.WEBGL = hasWebGL;
|
||||
ol.has.WEBGL_MAX_TEXTURE_SIZE = textureSize;
|
||||
}
|
||||
})();
|
||||
|
||||
22
src/ol/structs/checksum.js
Normal file
22
src/ol/structs/checksum.js
Normal file
@@ -0,0 +1,22 @@
|
||||
goog.provide('ol.structs.IHasChecksum');
|
||||
|
||||
|
||||
/**
|
||||
* @typedef {string}
|
||||
*/
|
||||
ol.structs.Checksum;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @interface
|
||||
*/
|
||||
ol.structs.IHasChecksum = function() {
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {string} The checksum.
|
||||
*/
|
||||
ol.structs.IHasChecksum.prototype.getChecksum = function() {
|
||||
};
|
||||
321
src/ol/style/atlasmanager.js
Normal file
321
src/ol/style/atlasmanager.js
Normal file
@@ -0,0 +1,321 @@
|
||||
goog.provide('ol.style.Atlas');
|
||||
goog.provide('ol.style.AtlasManager');
|
||||
|
||||
goog.require('goog.asserts');
|
||||
goog.require('goog.dom');
|
||||
goog.require('goog.dom.TagName');
|
||||
goog.require('goog.object');
|
||||
|
||||
|
||||
/**
|
||||
* Provides information for an image inside an atlas.
|
||||
* `offsetX` and `offsetY` are the position of the image inside
|
||||
* the atlas image `image`.
|
||||
* @typedef {{offsetX: number, offsetY: number, image: HTMLCanvasElement}}
|
||||
*/
|
||||
ol.style.AtlasInfo;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Manages the creation of image atlases.
|
||||
*
|
||||
* Images added to this manager will be inserted into an atlas, which
|
||||
* will be used for rendering.
|
||||
* The `size` given in the constructor is the size for the first
|
||||
* atlas. After that, when new atlases are created, they will have
|
||||
* twice the size as the latest atlas (until `maxSize` is reached).
|
||||
*
|
||||
* When used for WebGL, it is recommended to use `ol.has.WEBGL_MAX_TEXTURE_SIZE`
|
||||
* as `maxSize` value. Also, if an application uses a lot, or a lot of
|
||||
* large images, it is recommend to set a higher `size` value to avoid
|
||||
* the creation of too many atlases.
|
||||
*
|
||||
* @constructor
|
||||
* @struct
|
||||
* @api
|
||||
* @param {olx.style.AtlasManagerOptions=} opt_options Options.
|
||||
*/
|
||||
ol.style.AtlasManager = function(opt_options) {
|
||||
|
||||
var options = goog.isDef(opt_options) ? opt_options : {};
|
||||
|
||||
/**
|
||||
* The size in pixels of the latest atlas image.
|
||||
* @private
|
||||
* @type {number}
|
||||
*/
|
||||
this.currentSize_ = goog.isDef(options.size) ? options.size : 256;
|
||||
|
||||
/**
|
||||
* The maximum size in pixels of atlas images.
|
||||
* @private
|
||||
* @type {number}
|
||||
*/
|
||||
this.maxSize_ = goog.isDef(options.maxSize) ? options.maxSize : 2048;
|
||||
|
||||
/**
|
||||
* The size in pixels between images.
|
||||
* @private
|
||||
* @type {number}
|
||||
*/
|
||||
this.space_ = goog.isDef(options.space) ? options.space : 1;
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @type {Array.<ol.style.Atlas>}
|
||||
*/
|
||||
this.atlases_ = [new ol.style.Atlas(this.currentSize_, this.space_)];
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {string} id The identifier of the entry to check.
|
||||
* @return {?ol.style.AtlasInfo} The position and atlas image for the entry,
|
||||
* or `null` if the entry is not part of the atlas manager.
|
||||
*/
|
||||
ol.style.AtlasManager.prototype.getInfo = function(id) {
|
||||
var atlas, info, i, ii;
|
||||
for (i = 0, ii = this.atlases_.length; i < ii; ++i) {
|
||||
atlas = this.atlases_[i];
|
||||
info = atlas.get(id);
|
||||
if (info !== null) {
|
||||
return info;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Add an image to the atlas manager.
|
||||
*
|
||||
* If an entry for the given id already exists, the entry will
|
||||
* be overridden (but the space on the atlas graphic will not be freed).
|
||||
*
|
||||
* @param {string} id The identifier of the entry to add.
|
||||
* @param {number} width The width.
|
||||
* @param {number} height The height.
|
||||
* @param {function(CanvasRenderingContext2D, number, number)} renderCallback
|
||||
* Called to render the new image onto an atlas image.
|
||||
* @param {Object=} opt_this Value to use as `this` when executing
|
||||
* `renderCallback`.
|
||||
* @return {?ol.style.AtlasInfo} The position and atlas image for the entry,
|
||||
* or `null` if the image is too big.
|
||||
*/
|
||||
ol.style.AtlasManager.prototype.add =
|
||||
function(id, width, height, renderCallback, opt_this) {
|
||||
if (width + this.space_ > this.maxSize_ ||
|
||||
height + this.space_ > this.maxSize_) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var atlas, info, i, ii;
|
||||
for (i = 0, ii = this.atlases_.length; i < ii; ++i) {
|
||||
atlas = this.atlases_[i];
|
||||
info = atlas.add(id, width, height, renderCallback, opt_this);
|
||||
if (info !== null) {
|
||||
return info;
|
||||
} else if (info === null && i === ii - 1) {
|
||||
// the entry could not be added to one of the existing atlases,
|
||||
// create a new atlas that is twice as big and try to add to this one.
|
||||
this.currentSize_ = Math.min(this.currentSize_ * 2, this.maxSize_);
|
||||
atlas = new ol.style.Atlas(this.currentSize_, this.space_);
|
||||
this.atlases_.push(atlas);
|
||||
// run the loop another time
|
||||
++ii;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* This class facilitates the creation of image atlases.
|
||||
*
|
||||
* Images added to an atlas will be rendered onto a single
|
||||
* atlas canvas. The distribution of images on the canvas is
|
||||
* managed with the bin packing algorithm described in:
|
||||
* http://www.blackpawn.com/texts/lightmaps/
|
||||
*
|
||||
* @constructor
|
||||
* @struct
|
||||
* @param {number} size The size in pixels of the sprite image.
|
||||
* @param {number} space The space in pixels between images.
|
||||
*/
|
||||
ol.style.Atlas = function(size, space) {
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @type {number} The space in pixels between images.
|
||||
* Because texture coordinates are float values, the edges of
|
||||
* images might not be completely correct (in a way that the
|
||||
* edges overlap when being rendered). To avoid this we add a
|
||||
* padding around each image.
|
||||
*/
|
||||
this.space_ = space;
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @type {Array.<ol.style.Atlas.Block>}
|
||||
*/
|
||||
this.emptyBlocks_ = [{x: 0, y: 0, width: size, height: size}];
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @type {Object.<string, ol.style.AtlasInfo>}
|
||||
*/
|
||||
this.entries_ = {};
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @type {HTMLCanvasElement}
|
||||
*/
|
||||
this.canvas_ = /** @type {HTMLCanvasElement} */
|
||||
(goog.dom.createElement(goog.dom.TagName.CANVAS));
|
||||
this.canvas_.width = size;
|
||||
this.canvas_.height = size;
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @type {CanvasRenderingContext2D}
|
||||
*/
|
||||
this.context_ = /** @type {CanvasRenderingContext2D} */
|
||||
(this.canvas_.getContext('2d'));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {string} id The identifier of the entry to check.
|
||||
* @return {?ol.style.AtlasInfo}
|
||||
*/
|
||||
ol.style.Atlas.prototype.get = function(id) {
|
||||
return /** @type {?ol.style.AtlasInfo} */ (
|
||||
goog.object.get(this.entries_, id, null));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {string} id The identifier of the entry to add.
|
||||
* @param {number} width The width.
|
||||
* @param {number} height The height.
|
||||
* @param {function(CanvasRenderingContext2D, number, number)} renderCallback
|
||||
* Called to render the new image onto an atlas image.
|
||||
* @param {Object=} opt_this Value to use as `this` when executing
|
||||
* `renderCallback`.
|
||||
* @return {?ol.style.AtlasInfo} The position and atlas image for the entry.
|
||||
*/
|
||||
ol.style.Atlas.prototype.add =
|
||||
function(id, width, height, renderCallback, opt_this) {
|
||||
var block, i, ii;
|
||||
for (i = 0, ii = this.emptyBlocks_.length; i < ii; ++i) {
|
||||
block = this.emptyBlocks_[i];
|
||||
if (block.width >= width + this.space_ &&
|
||||
block.height >= height + this.space_) {
|
||||
// we found a block that is big enough for our entry
|
||||
var entry = {
|
||||
offsetX: block.x + this.space_,
|
||||
offsetY: block.y + this.space_,
|
||||
image: this.canvas_
|
||||
};
|
||||
this.entries_[id] = entry;
|
||||
|
||||
// render the image on the atlas image
|
||||
renderCallback.call(opt_this, this.context_,
|
||||
block.x + this.space_, block.y + this.space_);
|
||||
|
||||
// split the block after the insertion, either horizontally or vertically
|
||||
this.split_(i, block, width + this.space_, height + this.space_);
|
||||
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
|
||||
// there is no space for the new entry in this atlas
|
||||
return null;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {number} index The index of the block.
|
||||
* @param {ol.style.Atlas.Block} block The block to split.
|
||||
* @param {number} width The width of the entry to insert.
|
||||
* @param {number} height The height of the entry to insert.
|
||||
*/
|
||||
ol.style.Atlas.prototype.split_ =
|
||||
function(index, block, width, height) {
|
||||
var deltaWidth = block.width - width;
|
||||
var deltaHeight = block.height - height;
|
||||
|
||||
/** @type {ol.style.Atlas.Block} */
|
||||
var newBlock1;
|
||||
/** @type {ol.style.Atlas.Block} */
|
||||
var newBlock2;
|
||||
|
||||
if (deltaWidth > deltaHeight) {
|
||||
// split vertically
|
||||
// block right of the inserted entry
|
||||
newBlock1 = {
|
||||
x: block.x + width,
|
||||
y: block.y,
|
||||
width: block.width - width,
|
||||
height: block.height
|
||||
};
|
||||
|
||||
// block below the inserted entry
|
||||
newBlock2 = {
|
||||
x: block.x,
|
||||
y: block.y + height,
|
||||
width: width,
|
||||
height: block.height - height
|
||||
};
|
||||
this.updateBlocks_(index, newBlock1, newBlock2);
|
||||
} else {
|
||||
// split horizontally
|
||||
// block right of the inserted entry
|
||||
newBlock1 = {
|
||||
x: block.x + width,
|
||||
y: block.y,
|
||||
width: block.width - width,
|
||||
height: height
|
||||
};
|
||||
|
||||
// block below the inserted entry
|
||||
newBlock2 = {
|
||||
x: block.x,
|
||||
y: block.y + height,
|
||||
width: block.width,
|
||||
height: block.height - height
|
||||
};
|
||||
this.updateBlocks_(index, newBlock1, newBlock2);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Remove the old block and insert new blocks at the same array position.
|
||||
* The new blocks are inserted at the same position, so that splitted
|
||||
* blocks (that are potentially smaller) are filled first.
|
||||
* @private
|
||||
* @param {number} index The index of the block to remove.
|
||||
* @param {ol.style.Atlas.Block} newBlock1 The 1st block to add.
|
||||
* @param {ol.style.Atlas.Block} newBlock2 The 2nd block to add.
|
||||
*/
|
||||
ol.style.Atlas.prototype.updateBlocks_ =
|
||||
function(index, newBlock1, newBlock2) {
|
||||
var args = [index, 1];
|
||||
if (newBlock1.width > 0 && newBlock1.height > 0) {
|
||||
args.push(newBlock1);
|
||||
}
|
||||
if (newBlock2.width > 0 && newBlock2.height > 0) {
|
||||
args.push(newBlock2);
|
||||
}
|
||||
this.emptyBlocks_.splice.apply(this.emptyBlocks_, args);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @typedef {{x: number, y: number, width: number, height: number}}
|
||||
*/
|
||||
ol.style.Atlas.Block;
|
||||
@@ -4,6 +4,7 @@ goog.require('goog.dom');
|
||||
goog.require('goog.dom.TagName');
|
||||
goog.require('ol.color');
|
||||
goog.require('ol.render.canvas');
|
||||
goog.require('ol.structs.IHasChecksum');
|
||||
goog.require('ol.style.Fill');
|
||||
goog.require('ol.style.Image');
|
||||
goog.require('ol.style.ImageState');
|
||||
@@ -18,6 +19,7 @@ goog.require('ol.style.Stroke');
|
||||
* @constructor
|
||||
* @param {olx.style.CircleOptions=} opt_options Options.
|
||||
* @extends {ol.style.Image}
|
||||
* @implements {ol.structs.IHasChecksum}
|
||||
* @api
|
||||
*/
|
||||
ol.style.Circle = function(opt_options) {
|
||||
@@ -75,6 +77,12 @@ ol.style.Circle = function(opt_options) {
|
||||
*/
|
||||
this.size_ = [size, size];
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @type {Array.<ol.structs.Checksum>|null}
|
||||
*/
|
||||
this.checksums_ = null;
|
||||
|
||||
/**
|
||||
* @type {boolean}
|
||||
*/
|
||||
@@ -268,3 +276,27 @@ ol.style.Circle.prototype.render_ = function() {
|
||||
|
||||
return size;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
ol.style.Circle.prototype.getChecksum = function() {
|
||||
var strokeChecksum = !goog.isNull(this.stroke_) ?
|
||||
this.stroke_.getChecksum() : '-';
|
||||
var fillChecksum = !goog.isNull(this.fill_) ?
|
||||
this.fill_.getChecksum() : '-';
|
||||
|
||||
var recalculate = goog.isNull(this.checksums_) ||
|
||||
(strokeChecksum != this.checksums_[1] ||
|
||||
fillChecksum != this.checksums_[2] ||
|
||||
this.radius_ != this.checksums_[3]);
|
||||
|
||||
if (recalculate) {
|
||||
var checksum = 'c' + strokeChecksum + fillChecksum +
|
||||
(goog.isDef(this.radius_) ? this.radius_.toString() : '-');
|
||||
this.checksums_ = [checksum, strokeChecksum, fillChecksum, this.radius_];
|
||||
}
|
||||
|
||||
return this.checksums_[0];
|
||||
};
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
goog.provide('ol.style.Fill');
|
||||
|
||||
goog.require('ol.color');
|
||||
goog.require('ol.structs.IHasChecksum');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
@@ -8,6 +11,7 @@ goog.provide('ol.style.Fill');
|
||||
*
|
||||
* @constructor
|
||||
* @param {olx.style.FillOptions=} opt_options Options.
|
||||
* @implements {ol.structs.IHasChecksum}
|
||||
* @api
|
||||
*/
|
||||
ol.style.Fill = function(opt_options) {
|
||||
@@ -19,6 +23,12 @@ ol.style.Fill = function(opt_options) {
|
||||
* @type {ol.Color|string}
|
||||
*/
|
||||
this.color_ = goog.isDef(options.color) ? options.color : null;
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @type {?ol.structs.Checksum}
|
||||
*/
|
||||
this.checksum_ = null;
|
||||
};
|
||||
|
||||
|
||||
@@ -39,4 +49,18 @@ ol.style.Fill.prototype.getColor = function() {
|
||||
*/
|
||||
ol.style.Fill.prototype.setColor = function(color) {
|
||||
this.color_ = color;
|
||||
this.checksum_ = null;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
ol.style.Fill.prototype.getChecksum = function() {
|
||||
if (goog.isNull(this.checksum_)) {
|
||||
this.checksum_ = 'f' + (!goog.isNull(this.color_) ?
|
||||
ol.color.asString(this.color_) : '-');
|
||||
}
|
||||
|
||||
return this.checksum_;
|
||||
};
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
goog.provide('ol.style.Stroke');
|
||||
|
||||
goog.require('goog.crypt');
|
||||
goog.require('goog.crypt.Md5');
|
||||
goog.require('ol.color');
|
||||
goog.require('ol.structs.IHasChecksum');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
@@ -11,6 +16,7 @@ goog.provide('ol.style.Stroke');
|
||||
*
|
||||
* @constructor
|
||||
* @param {olx.style.StrokeOptions=} opt_options Options.
|
||||
* @implements {ol.structs.IHasChecksum}
|
||||
* @api
|
||||
*/
|
||||
ol.style.Stroke = function(opt_options) {
|
||||
@@ -52,6 +58,12 @@ ol.style.Stroke = function(opt_options) {
|
||||
* @type {number|undefined}
|
||||
*/
|
||||
this.width_ = options.width;
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @type {?ol.structs.Checksum}
|
||||
*/
|
||||
this.checksum_ = null;
|
||||
};
|
||||
|
||||
|
||||
@@ -117,6 +129,7 @@ ol.style.Stroke.prototype.getWidth = function() {
|
||||
*/
|
||||
ol.style.Stroke.prototype.setColor = function(color) {
|
||||
this.color_ = color;
|
||||
this.checksum_ = null;
|
||||
};
|
||||
|
||||
|
||||
@@ -128,6 +141,7 @@ ol.style.Stroke.prototype.setColor = function(color) {
|
||||
*/
|
||||
ol.style.Stroke.prototype.setLineCap = function(lineCap) {
|
||||
this.lineCap_ = lineCap;
|
||||
this.checksum_ = null;
|
||||
};
|
||||
|
||||
|
||||
@@ -139,6 +153,7 @@ ol.style.Stroke.prototype.setLineCap = function(lineCap) {
|
||||
*/
|
||||
ol.style.Stroke.prototype.setLineDash = function(lineDash) {
|
||||
this.lineDash_ = lineDash;
|
||||
this.checksum_ = null;
|
||||
};
|
||||
|
||||
|
||||
@@ -150,6 +165,7 @@ ol.style.Stroke.prototype.setLineDash = function(lineDash) {
|
||||
*/
|
||||
ol.style.Stroke.prototype.setLineJoin = function(lineJoin) {
|
||||
this.lineJoin_ = lineJoin;
|
||||
this.checksum_ = null;
|
||||
};
|
||||
|
||||
|
||||
@@ -161,6 +177,7 @@ ol.style.Stroke.prototype.setLineJoin = function(lineJoin) {
|
||||
*/
|
||||
ol.style.Stroke.prototype.setMiterLimit = function(miterLimit) {
|
||||
this.miterLimit_ = miterLimit;
|
||||
this.checksum_ = null;
|
||||
};
|
||||
|
||||
|
||||
@@ -172,4 +189,33 @@ ol.style.Stroke.prototype.setMiterLimit = function(miterLimit) {
|
||||
*/
|
||||
ol.style.Stroke.prototype.setWidth = function(width) {
|
||||
this.width_ = width;
|
||||
this.checksum_ = null;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
ol.style.Stroke.prototype.getChecksum = function() {
|
||||
if (goog.isNull(this.checksum_)) {
|
||||
var raw = 's' +
|
||||
(!goog.isNull(this.color_) ?
|
||||
ol.color.asString(this.color_) : '-') + ',' +
|
||||
(goog.isDef(this.lineCap_) ?
|
||||
this.lineCap_.toString() : '-') + ',' +
|
||||
(!goog.isNull(this.lineDash_) ?
|
||||
this.lineDash_.toString() : '-') + ',' +
|
||||
(goog.isDef(this.lineJoin_) ?
|
||||
this.lineJoin_ : '-') + ',' +
|
||||
(goog.isDef(this.miterLimit_) ?
|
||||
this.miterLimit_.toString() : '-') + ',' +
|
||||
(goog.isDef(this.width_) ?
|
||||
this.width_.toString() : '-');
|
||||
|
||||
var md5 = new goog.crypt.Md5();
|
||||
md5.update(raw);
|
||||
this.checksum_ = goog.crypt.byteArrayToString(md5.digest());
|
||||
}
|
||||
|
||||
return this.checksum_;
|
||||
};
|
||||
|
||||
245
test/spec/ol/style/atlasmanager.test.js
Normal file
245
test/spec/ol/style/atlasmanager.test.js
Normal file
@@ -0,0 +1,245 @@
|
||||
goog.provide('ol.test.style.AtlasManager');
|
||||
|
||||
|
||||
describe('ol.style.Atlas', function() {
|
||||
|
||||
var defaultRender = function(context, x, y) {
|
||||
};
|
||||
|
||||
describe('#constructor', function() {
|
||||
|
||||
it('inits the atlas', function() {
|
||||
var atlas = new ol.style.Atlas(256, 1);
|
||||
expect(atlas.emptyBlocks_).to.eql(
|
||||
[{x: 0, y: 0, width: 256, height: 256}]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#add (squares with same size)', function() {
|
||||
|
||||
it('adds one entry', function() {
|
||||
var atlas = new ol.style.Atlas(128, 1);
|
||||
var info = atlas.add('1', 32, 32, defaultRender);
|
||||
|
||||
expect(info).to.eql(
|
||||
{offsetX: 1, offsetY: 1, image: atlas.canvas_});
|
||||
|
||||
expect(atlas.get('1')).to.eql(info);
|
||||
});
|
||||
|
||||
it('adds two entries', function() {
|
||||
var atlas = new ol.style.Atlas(128, 1);
|
||||
|
||||
atlas.add('1', 32, 32, defaultRender);
|
||||
var info = atlas.add('2', 32, 32, defaultRender);
|
||||
|
||||
expect(info).to.eql(
|
||||
{offsetX: 34, offsetY: 1, image: atlas.canvas_});
|
||||
|
||||
expect(atlas.get('2')).to.eql(info);
|
||||
});
|
||||
|
||||
it('adds three entries', function() {
|
||||
var atlas = new ol.style.Atlas(128, 1);
|
||||
|
||||
atlas.add('1', 32, 32, defaultRender);
|
||||
atlas.add('2', 32, 32, defaultRender);
|
||||
var info = atlas.add('3', 32, 32, defaultRender);
|
||||
|
||||
expect(info).to.eql(
|
||||
{offsetX: 67, offsetY: 1, image: atlas.canvas_});
|
||||
|
||||
expect(atlas.get('3')).to.eql(info);
|
||||
});
|
||||
|
||||
it('adds four entries (new row)', function() {
|
||||
var atlas = new ol.style.Atlas(128, 1);
|
||||
|
||||
atlas.add('1', 32, 32, defaultRender);
|
||||
atlas.add('2', 32, 32, defaultRender);
|
||||
atlas.add('3', 32, 32, defaultRender);
|
||||
var info = atlas.add('4', 32, 32, defaultRender);
|
||||
|
||||
expect(info).to.eql(
|
||||
{offsetX: 1, offsetY: 34, image: atlas.canvas_});
|
||||
|
||||
expect(atlas.get('4')).to.eql(info);
|
||||
});
|
||||
|
||||
it('returns null when an entry is too big', function() {
|
||||
var atlas = new ol.style.Atlas(128, 1);
|
||||
|
||||
atlas.add('1', 32, 32, defaultRender);
|
||||
atlas.add('2', 32, 32, defaultRender);
|
||||
atlas.add('3', 32, 32, defaultRender);
|
||||
var info = atlas.add(4, 100, 100, defaultRender);
|
||||
|
||||
expect(info).to.eql(null);
|
||||
});
|
||||
|
||||
it('fills up the whole atlas', function() {
|
||||
var atlas = new ol.style.Atlas(128, 1);
|
||||
|
||||
for (var i = 1; i <= 16; i++) {
|
||||
expect(atlas.add(i.toString(), 28, 28, defaultRender)).to.be.ok();
|
||||
}
|
||||
|
||||
// there is no more space for items of this size, the next one will fail
|
||||
expect(atlas.add('17', 28, 28, defaultRender)).to.eql(null);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#add (rectangles with different sizes)', function() {
|
||||
|
||||
it('adds a bunch of rectangles', function() {
|
||||
var atlas = new ol.style.Atlas(128, 1);
|
||||
|
||||
expect(atlas.add('1', 64, 32, defaultRender)).to.eql(
|
||||
{offsetX: 1, offsetY: 1, image: atlas.canvas_});
|
||||
|
||||
expect(atlas.add('2', 64, 32, defaultRender)).to.eql(
|
||||
{offsetX: 1, offsetY: 34, image: atlas.canvas_});
|
||||
|
||||
expect(atlas.add('3', 64, 32, defaultRender)).to.eql(
|
||||
{offsetX: 1, offsetY: 67, image: atlas.canvas_});
|
||||
|
||||
// this one can not be added anymore
|
||||
expect(atlas.add('4', 64, 32, defaultRender)).to.eql(null);
|
||||
|
||||
// but there is still room for smaller ones
|
||||
expect(atlas.add('5', 40, 32, defaultRender)).to.eql(
|
||||
{offsetX: 66, offsetY: 1, image: atlas.canvas_});
|
||||
|
||||
expect(atlas.add('6', 40, 32, defaultRender)).to.eql(
|
||||
{offsetX: 66, offsetY: 34, image: atlas.canvas_});
|
||||
});
|
||||
|
||||
it('fills up the whole atlas (rectangles in portrait format)', function() {
|
||||
var atlas = new ol.style.Atlas(128, 1);
|
||||
|
||||
for (var i = 1; i <= 32; i++) {
|
||||
expect(atlas.add(i.toString(), 28, 14, defaultRender)).to.be.ok();
|
||||
}
|
||||
|
||||
// there is no more space for items of this size, the next one will fail
|
||||
expect(atlas.add('33', 28, 14, defaultRender)).to.eql(null);
|
||||
});
|
||||
|
||||
it('fills up the whole atlas (rectangles in landscape format)', function() {
|
||||
var atlas = new ol.style.Atlas(128, 1);
|
||||
|
||||
for (var i = 1; i <= 32; i++) {
|
||||
expect(atlas.add(i.toString(), 14, 28, defaultRender)).to.be.ok();
|
||||
}
|
||||
|
||||
// there is no more space for items of this size, the next one will fail
|
||||
expect(atlas.add('33', 14, 28, defaultRender)).to.eql(null);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#add (rendering)', function() {
|
||||
|
||||
it('calls the render callback with the right values', function() {
|
||||
var atlas = new ol.style.Atlas(128, 1);
|
||||
var rendererCallback = sinon.spy();
|
||||
atlas.add('1', 32, 32, rendererCallback);
|
||||
|
||||
expect(rendererCallback.calledOnce).to.be.ok();
|
||||
expect(rendererCallback.calledWith(atlas.context_, 1, 1)).to.be.ok();
|
||||
|
||||
rendererCallback = sinon.spy();
|
||||
atlas.add('2', 32, 32, rendererCallback);
|
||||
|
||||
expect(rendererCallback.calledOnce).to.be.ok();
|
||||
expect(rendererCallback.calledWith(atlas.context_, 34, 1)).to.be.ok();
|
||||
});
|
||||
|
||||
it('is possible to actually draw on the canvas', function() {
|
||||
var atlas = new ol.style.Atlas(128, 1);
|
||||
|
||||
var rendererCallback = function(context, x, y) {
|
||||
context.fillStyle = '#FFA500';
|
||||
context.fillRect(x, y, 32, 32);
|
||||
};
|
||||
|
||||
expect(atlas.add('1', 32, 32, rendererCallback)).to.be.ok();
|
||||
expect(atlas.add('2', 32, 32, rendererCallback)).to.be.ok();
|
||||
// no error, ok
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('ol.style.AtlasManager', function() {
|
||||
|
||||
var defaultRender = function(context, x, y) {
|
||||
};
|
||||
|
||||
describe('#constructor', function() {
|
||||
|
||||
it('inits the atlas manager', function() {
|
||||
var manager = new ol.style.AtlasManager();
|
||||
expect(manager.atlases_).to.not.be.empty();
|
||||
});
|
||||
});
|
||||
|
||||
describe('#add', function() {
|
||||
|
||||
it('adds one entry', function() {
|
||||
var manager = new ol.style.AtlasManager({size: 128});
|
||||
var info = manager.add('1', 32, 32, defaultRender);
|
||||
|
||||
expect(info).to.eql(
|
||||
{offsetX: 1, offsetY: 1, image: manager.atlases_[0].canvas_});
|
||||
|
||||
expect(manager.getInfo('1')).to.eql(info);
|
||||
});
|
||||
|
||||
it('creates a new atlas if needed', function() {
|
||||
var manager = new ol.style.AtlasManager({size: 128});
|
||||
expect(manager.add('1', 100, 100, defaultRender)).to.be.ok();
|
||||
var info = manager.add('2', 100, 100, defaultRender);
|
||||
expect(info).to.be.ok();
|
||||
expect(info.image.width).to.eql(256);
|
||||
expect(manager.atlases_).to.have.length(2);
|
||||
});
|
||||
|
||||
it('creates new atlases until one is large enough', function() {
|
||||
var manager = new ol.style.AtlasManager({size: 128});
|
||||
expect(manager.add('1', 100, 100, defaultRender)).to.be.ok();
|
||||
expect(manager.atlases_).to.have.length(1);
|
||||
var info = manager.add('2', 500, 500, defaultRender);
|
||||
expect(info).to.be.ok();
|
||||
expect(info.image.width).to.eql(512);
|
||||
expect(manager.atlases_).to.have.length(3);
|
||||
});
|
||||
|
||||
it('checks all existing atlases and create a new if needed', function() {
|
||||
var manager = new ol.style.AtlasManager({size: 128});
|
||||
expect(manager.add('1', 100, 100, defaultRender)).to.be.ok();
|
||||
expect(manager.add('2', 100, 100, defaultRender)).to.be.ok();
|
||||
expect(manager.atlases_).to.have.length(2);
|
||||
var info = manager.add(3, 500, 500, defaultRender);
|
||||
expect(info).to.be.ok();
|
||||
expect(info.image.width).to.eql(512);
|
||||
expect(manager.atlases_).to.have.length(3);
|
||||
});
|
||||
|
||||
it('returns null if the size exceeds the maximum size', function() {
|
||||
var manager = new ol.style.AtlasManager({size: 128});
|
||||
expect(manager.add('1', 100, 100, defaultRender)).to.be.ok();
|
||||
expect(manager.add('2', 2048, 2048, defaultRender)).to.eql(null);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#getInfo', function() {
|
||||
|
||||
it('returns null if no entry for the given id', function() {
|
||||
var manager = new ol.style.AtlasManager({size: 128});
|
||||
expect(manager.getInfo('123456')).to.eql(null);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
goog.require('ol.style.Atlas');
|
||||
goog.require('ol.style.AtlasManager');
|
||||
167
test/spec/ol/style/circlestyle.test.js
Normal file
167
test/spec/ol/style/circlestyle.test.js
Normal file
@@ -0,0 +1,167 @@
|
||||
goog.provide('ol.test.style.Circle');
|
||||
|
||||
|
||||
describe('ol.style.Circle', function() {
|
||||
|
||||
describe('#getChecksum', function() {
|
||||
|
||||
it('calculates the same hash code for default options', function() {
|
||||
var style1 = new ol.style.Circle();
|
||||
var style2 = new ol.style.Circle();
|
||||
expect(style1.getChecksum()).to.eql(style2.getChecksum());
|
||||
});
|
||||
|
||||
it('calculates not the same hash code (radius)', function() {
|
||||
var style1 = new ol.style.Circle();
|
||||
var style2 = new ol.style.Circle({
|
||||
radius: 5
|
||||
});
|
||||
expect(style1.getChecksum()).to.not.eql(style2.getChecksum());
|
||||
});
|
||||
|
||||
it('calculates the same hash code (radius)', function() {
|
||||
var style1 = new ol.style.Circle({
|
||||
radius: 5
|
||||
});
|
||||
var style2 = new ol.style.Circle({
|
||||
radius: 5
|
||||
});
|
||||
expect(style1.getChecksum()).to.eql(style2.getChecksum());
|
||||
});
|
||||
|
||||
it('calculates not the same hash code (color)', function() {
|
||||
var style1 = new ol.style.Circle({
|
||||
radius: 5,
|
||||
fill: new ol.style.Fill({
|
||||
color: '#319FD3'
|
||||
})
|
||||
});
|
||||
var style2 = new ol.style.Circle({
|
||||
radius: 5,
|
||||
stroke: new ol.style.Stroke({
|
||||
color: '#319FD3'
|
||||
})
|
||||
});
|
||||
expect(style1.getChecksum()).to.not.eql(style2.getChecksum());
|
||||
});
|
||||
|
||||
it('calculates the same hash code (everything set)', function() {
|
||||
var style1 = new ol.style.Circle({
|
||||
radius: 5,
|
||||
fill: new ol.style.Fill({
|
||||
color: '#319FD3'
|
||||
}),
|
||||
stroke: new ol.style.Stroke({
|
||||
color: '#319FD3',
|
||||
lineCap: 'round',
|
||||
lineDash: [5, 15, 25],
|
||||
lineJoin: 'miter',
|
||||
miterLimit: 4,
|
||||
width: 2
|
||||
})
|
||||
});
|
||||
var style2 = new ol.style.Circle({
|
||||
radius: 5,
|
||||
fill: new ol.style.Fill({
|
||||
color: '#319FD3'
|
||||
}),
|
||||
stroke: new ol.style.Stroke({
|
||||
color: '#319FD3',
|
||||
lineCap: 'round',
|
||||
lineDash: [5, 15, 25],
|
||||
lineJoin: 'miter',
|
||||
miterLimit: 4,
|
||||
width: 2
|
||||
})
|
||||
});
|
||||
expect(style1.getChecksum()).to.eql(style2.getChecksum());
|
||||
});
|
||||
|
||||
it('calculates not the same hash code (stroke width differs)', function() {
|
||||
var style1 = new ol.style.Circle({
|
||||
radius: 5,
|
||||
fill: new ol.style.Fill({
|
||||
color: '#319FD3'
|
||||
}),
|
||||
stroke: new ol.style.Stroke({
|
||||
color: '#319FD3',
|
||||
lineCap: 'round',
|
||||
lineDash: [5, 15, 25],
|
||||
lineJoin: 'miter',
|
||||
miterLimit: 4,
|
||||
width: 3
|
||||
})
|
||||
});
|
||||
var style2 = new ol.style.Circle({
|
||||
radius: 5,
|
||||
fill: new ol.style.Fill({
|
||||
color: '#319FD3'
|
||||
}),
|
||||
stroke: new ol.style.Stroke({
|
||||
color: '#319FD3',
|
||||
lineCap: 'round',
|
||||
lineDash: [5, 15, 25],
|
||||
lineJoin: 'miter',
|
||||
miterLimit: 4,
|
||||
width: 2
|
||||
})
|
||||
});
|
||||
expect(style1.getChecksum()).to.not.eql(style2.getChecksum());
|
||||
});
|
||||
|
||||
it('invalidates a cached checksum if values change (fill)', function() {
|
||||
var style1 = new ol.style.Circle({
|
||||
radius: 5,
|
||||
fill: new ol.style.Fill({
|
||||
color: '#319FD3'
|
||||
}),
|
||||
stroke: new ol.style.Stroke({
|
||||
color: '#319FD3'
|
||||
})
|
||||
});
|
||||
var style2 = new ol.style.Circle({
|
||||
radius: 5,
|
||||
fill: new ol.style.Fill({
|
||||
color: '#319FD3'
|
||||
}),
|
||||
stroke: new ol.style.Stroke({
|
||||
color: '#319FD3'
|
||||
})
|
||||
});
|
||||
expect(style1.getChecksum()).to.eql(style2.getChecksum());
|
||||
|
||||
style1.getFill().setColor('red');
|
||||
expect(style1.getChecksum()).to.not.eql(style2.getChecksum());
|
||||
});
|
||||
|
||||
it('invalidates a cached checksum if values change (stroke)', function() {
|
||||
var style1 = new ol.style.Circle({
|
||||
radius: 5,
|
||||
fill: new ol.style.Fill({
|
||||
color: '#319FD3'
|
||||
}),
|
||||
stroke: new ol.style.Stroke({
|
||||
color: '#319FD3'
|
||||
})
|
||||
});
|
||||
var style2 = new ol.style.Circle({
|
||||
radius: 5,
|
||||
fill: new ol.style.Fill({
|
||||
color: '#319FD3'
|
||||
}),
|
||||
stroke: new ol.style.Stroke({
|
||||
color: '#319FD3'
|
||||
})
|
||||
});
|
||||
expect(style1.getChecksum()).to.eql(style2.getChecksum());
|
||||
|
||||
style1.getStroke().setWidth(4);
|
||||
expect(style1.getChecksum()).to.not.eql(style2.getChecksum());
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
goog.require('ol.style.Circle');
|
||||
goog.require('ol.style.Fill');
|
||||
goog.require('ol.style.Stroke');
|
||||
Reference in New Issue
Block a user