Merge pull request #2967 from camptocamp/webgl-point

Add support for drawing points with WebGL
This commit is contained in:
Éric Lemoine
2014-12-04 13:11:31 +01:00
46 changed files with 4403 additions and 1741 deletions

BIN
examples/data/Butterfly.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

View File

@@ -38,6 +38,7 @@ var vector = new ol.layer.Vector({
var map = new ol.Map({
layers: [raster, vector],
renderer: exampleNS.getRendererFromQueryString(),
target: 'map',
view: new ol.View({
center: [-11000000, 4600000],

View File

@@ -1,11 +1,14 @@
goog.require('ol.Feature');
goog.require('ol.Map');
goog.require('ol.View');
goog.require('ol.geom.MultiPoint');
goog.require('ol.geom.Point');
goog.require('ol.layer.Tile');
goog.require('ol.source.MapQuest');
goog.require('ol.style.Circle');
goog.require('ol.style.Fill');
goog.require('ol.style.Stroke');
goog.require('ol.style.Style');
var map = new ol.Map({
@@ -14,6 +17,7 @@ var map = new ol.Map({
source: new ol.source.MapQuest({layer: 'sat'})
})
],
renderer: exampleNS.getRendererFromQueryString(),
target: 'map',
view: new ol.View({
center: [0, 0],
@@ -28,6 +32,20 @@ var imageStyle = new ol.style.Circle({
stroke: new ol.style.Stroke({color: 'red', width: 1})
});
var headInnerImageStyle = new ol.style.Style({
image: new ol.style.Circle({
radius: 2,
snapToPixel: false,
fill: new ol.style.Fill({color: 'blue'})
})
});
var headOuterImageStyle = new ol.style.Circle({
radius: 5,
snapToPixel: false,
fill: new ol.style.Fill({color: 'black'})
});
var n = 200;
var omegaTheta = 30000; // Rotation period in ms
var R = 7e6;
@@ -48,6 +66,14 @@ map.on('postcompose', function(event) {
vectorContext.setImageStyle(imageStyle);
vectorContext.drawMultiPointGeometry(
new ol.geom.MultiPoint(coordinates), null);
var headPoint = new ol.geom.Point(coordinates[coordinates.length - 1]);
var headFeature = new ol.Feature(headPoint);
vectorContext.drawFeature(headFeature, headInnerImageStyle);
vectorContext.setImageStyle(headOuterImageStyle);
vectorContext.drawMultiPointGeometry(headPoint, null);
map.render();
});
map.render();

View File

@@ -0,0 +1,53 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="chrome=1">
<meta name="viewport" content="initial-scale=1.0, user-scalable=no, width=device-width">
<link rel="stylesheet" href="../css/ol.css" type="text/css">
<link rel="stylesheet" href="../resources/bootstrap/css/bootstrap.min.css" type="text/css">
<link rel="stylesheet" href="../resources/layout.css" type="text/css">
<link rel="stylesheet" href="../resources/bootstrap/css/bootstrap-responsive.min.css" type="text/css">
<title>Icon sprites with WebGL example</title>
</head>
<body>
<div class="navbar navbar-inverse navbar-fixed-top">
<div class="navbar-inner">
<div class="container">
<a class="brand" href="./"><img src="../resources/logo.png"> OpenLayers 3 Examples</a>
</div>
</div>
</div>
<div class="container-fluid">
<div class="row-fluid">
<div class="span12">
<div id="map" class="map"></div>
</div>
</div>
<div class="row-fluid">
<div class="span12">
<h4 id="title">Icon sprite with WebGL example</h4>
<p id="shortdesc">Icon sprite with WebGL.</p>
<div id="docs">
<p>See the <a href="icon-sprite-webgl.js" target="_blank">icon-sprite-webgl.js source</a> to see how this is done.</p>
<p>In this example a sprite image is used for the icon styles. Using a sprite is required to get good performance with WebGL.</p>
</div>
<div id="tags">webgl, icon, sprite, vector, point</div>
</div>
</div>
</div>
<script src="../resources/jquery.min.js" type="text/javascript"></script>
<script src="../resources/bootstrap/js/bootstrap.min.js" type="text/javascript"></script>
<script src="../resources/example-behaviour.js" type="text/javascript"></script>
<script src="loader.js?id=icon-sprite-webgl" type="text/javascript"></script>
</body>
</html>

View File

@@ -0,0 +1,111 @@
goog.require('ol.Feature');
goog.require('ol.FeatureOverlay');
goog.require('ol.Map');
goog.require('ol.View');
goog.require('ol.geom.Point');
goog.require('ol.layer.Vector');
goog.require('ol.source.Vector');
goog.require('ol.style.Icon');
goog.require('ol.style.Style');
var iconInfo = [{
offset: [0, 0],
opacity: 1.0,
rotateWithView: true,
rotation: 0.0,
scale: 1.0,
size: [55, 55]
}, {
offset: [110, 86],
opacity: 0.75,
rotateWithView: false,
rotation: Math.PI / 2.0,
scale: 1.25,
size: [55, 55]
}, {
offset: [55, 0],
opacity: 0.5,
rotateWithView: true,
rotation: Math.PI / 3.0,
scale: 1.5,
size: [55, 86]
}, {
offset: [212, 0],
opacity: 1.0,
rotateWithView: true,
rotation: 0.0,
scale: 1.0,
size: [44, 44]
}];
var i;
var iconCount = iconInfo.length;
var icons = new Array(iconCount);
for (i = 0; i < iconCount; ++i) {
var info = iconInfo[i];
icons[i] = new ol.style.Icon({
offset: info.offset,
opacity: info.opacity,
rotateWithView: info.rotateWithView,
rotation: info.rotation,
scale: info.scale,
size: info.size,
src: 'data/Butterfly.png'
});
}
var featureCount = 50000;
var features = new Array(featureCount);
var feature, geometry;
var e = 25000000;
for (i = 0; i < featureCount; ++i) {
geometry = new ol.geom.Point(
[2 * e * Math.random() - e, 2 * e * Math.random() - e]);
feature = new ol.Feature(geometry);
feature.setStyle(
new ol.style.Style({
image: icons[i % (iconCount - 1)]
})
);
features[i] = feature;
}
var vectorSource = new ol.source.Vector({
features: features
});
var vector = new ol.layer.Vector({
source: vectorSource
});
// Use the "webgl" renderer by default.
var renderer = exampleNS.getRendererFromQueryString();
if (!renderer) {
renderer = 'webgl';
}
var map = new ol.Map({
renderer: renderer,
layers: [vector],
target: document.getElementById('map'),
view: new ol.View({
center: [0, 0],
zoom: 5
})
});
var overlayFeatures = [];
for (i = 0; i < featureCount; i += 30) {
var clone = features[i].clone();
clone.setStyle(null);
overlayFeatures.push(clone);
}
var featureOverlay = new ol.FeatureOverlay({
map: map,
style: new ol.style.Style({
image: icons[iconCount - 1]
}),
features: overlayFeatures
});

View File

@@ -0,0 +1,58 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="chrome=1">
<meta name="viewport" content="initial-scale=1.0, user-scalable=no, width=device-width">
<link rel="stylesheet" href="../css/ol.css" type="text/css">
<link rel="stylesheet" href="../resources/bootstrap/css/bootstrap.min.css" type="text/css">
<link rel="stylesheet" href="../resources/layout.css" type="text/css">
<link rel="stylesheet" href="../resources/bootstrap/css/bootstrap-responsive.min.css" type="text/css">
<title>Symbols with WebGL example</title>
</head>
<body>
<div class="navbar navbar-inverse navbar-fixed-top">
<div class="navbar-inner">
<div class="container">
<a class="brand" href="./"><img src="../resources/logo.png"> OpenLayers 3 Examples</a>
</div>
</div>
</div>
<div class="container-fluid">
<div class="row-fluid">
<div class="span12">
<div id="map" class="map"></div>
</div>
</div>
<div class="row-fluid">
<div class="span12">
<h4 id="title">Symbols with WebGL example</h4>
<p id="shortdesc">Using symbols in an atlas with WebGL.</p>
<div id="docs">
<p>When using symbol styles with WebGL, OpenLayers would render the symbol
on a temporary image and would create a WebGL texture for each image. For a
better performance, it is recommended to use atlas images (similar to
image sprites with CSS), so that the number of textures is reduced. OpenLayers
provides an <code>AtlasManager</code>, which when passed to the constructor
of a symbol style, will create atlases for the symbols.</p>
<p>See the <a href="symbol-atlas-webgl.js" target="_blank">symbol-atlas-webgl.js source</a> to see how this is done.</p>
</div>
<div id="tags">webgl, symbol, atlas, vector, point</div>
</div>
</div>
</div>
<script src="../resources/jquery.min.js" type="text/javascript"></script>
<script src="../resources/bootstrap/js/bootstrap.min.js" type="text/javascript"></script>
<script src="../resources/example-behaviour.js" type="text/javascript"></script>
<script src="loader.js?id=symbol-atlas-webgl" type="text/javascript"></script>
</body>
</html>

View File

@@ -0,0 +1,123 @@
goog.require('ol.Feature');
goog.require('ol.Map');
goog.require('ol.View');
goog.require('ol.geom.Point');
goog.require('ol.layer.Vector');
goog.require('ol.source.Vector');
goog.require('ol.style.AtlasManager');
goog.require('ol.style.Circle');
goog.require('ol.style.Fill');
goog.require('ol.style.RegularShape');
goog.require('ol.style.Stroke');
goog.require('ol.style.Style');
var atlasManager = new ol.style.AtlasManager({
// we increase the initial size so that all symbols fit into
// a single atlas image
initialSize: 512
});
var symbolInfo = [{
opacity: 1.0,
scale: 1.0,
fillColor: 'rgba(255, 153, 0, 0.4)',
strokeColor: 'rgba(255, 204, 0, 0.2)'
}, {
opacity: 0.75,
scale: 1.25,
fillColor: 'rgba(70, 80, 224, 0.4)',
strokeColor: 'rgba(12, 21, 138, 0.2)'
}, {
opacity: 0.5,
scale: 1.5,
fillColor: 'rgba(66, 150, 79, 0.4)',
strokeColor: 'rgba(20, 99, 32, 0.2)'
}, {
opacity: 1.0,
scale: 1.0,
fillColor: 'rgba(176, 61, 35, 0.4)',
strokeColor: 'rgba(145, 43, 20, 0.2)'
}];
var radiuses = [3, 6, 9, 15, 19, 25];
var symbolCount = symbolInfo.length * radiuses.length * 2;
var symbols = [];
var i, j;
for (i = 0; i < symbolInfo.length; ++i) {
var info = symbolInfo[i];
for (j = 0; j < radiuses.length; ++j) {
// circle symbol
symbols.push(new ol.style.Circle({
opacity: info.opacity,
scale: info.scale,
radius: radiuses[j],
fill: new ol.style.Fill({
color: info.fillColor
}),
stroke: new ol.style.Stroke({
color: info.strokeColor,
width: 1
}),
// by passing the atlas manager to the symbol,
// the symbol will be added to an atlas
atlasManager: atlasManager
}));
// star symbol
symbols.push(new ol.style.RegularShape({
points: 8,
opacity: info.opacity,
scale: info.scale,
radius: radiuses[j],
radius2: radiuses[j] * 0.7,
angle: 1.4,
fill: new ol.style.Fill({
color: info.fillColor
}),
stroke: new ol.style.Stroke({
color: info.strokeColor,
width: 1
}),
atlasManager: atlasManager
}));
}
}
var featureCount = 50000;
var features = new Array(featureCount);
var feature, geometry;
var e = 25000000;
for (i = 0; i < featureCount; ++i) {
geometry = new ol.geom.Point(
[2 * e * Math.random() - e, 2 * e * Math.random() - e]);
feature = new ol.Feature(geometry);
feature.setStyle(
new ol.style.Style({
image: symbols[i % symbolCount]
})
);
features[i] = feature;
}
var vectorSource = new ol.source.Vector({
features: features
});
var vector = new ol.layer.Vector({
source: vectorSource
});
// Use the "webgl" renderer by default.
var renderer = exampleNS.getRendererFromQueryString();
if (!renderer) {
renderer = 'webgl';
}
var map = new ol.Map({
renderer: renderer,
layers: [vector],
target: document.getElementById('map'),
view: new ol.View({
center: [0, 0],
zoom: 4
})
});

View File

@@ -5438,7 +5438,8 @@ olx.style;
* @typedef {{fill: (ol.style.Fill|undefined),
* radius: number,
* snapToPixel: (boolean|undefined),
* stroke: (ol.style.Stroke|undefined)}}
* stroke: (ol.style.Stroke|undefined),
* atlasManager: (ol.style.AtlasManager|undefined)}}
* @api
*/
olx.style.CircleOptions;
@@ -5482,6 +5483,16 @@ olx.style.CircleOptions.prototype.snapToPixel;
olx.style.CircleOptions.prototype.stroke;
/**
* The atlas manager to use for this circle. When using WebGL it is
* recommended to use an atlas manager to avoid texture switching.
* If an atlas manager is given, the circle is added to an atlas.
* By default no atlas manager is used.
* @type {ol.style.AtlasManager|undefined}
*/
olx.style.CircleOptions.prototype.atlasManager;
/**
* @typedef {{color: (ol.Color|string|undefined)}}
* @api
@@ -5661,7 +5672,8 @@ olx.style.IconOptions.prototype.src;
* radius2: (number|undefined),
* angle: (number|undefined),
* snapToPixel: (boolean|undefined),
* stroke: (ol.style.Stroke|undefined)}}
* stroke: (ol.style.Stroke|undefined),
* atlasManager: (ol.style.AtlasManager|undefined)}}
* @api
*/
olx.style.RegularShapeOptions;
@@ -5740,6 +5752,16 @@ olx.style.RegularShapeOptions.prototype.snapToPixel;
olx.style.RegularShapeOptions.prototype.stroke;
/**
* The atlas manager to use for this symbol. When using WebGL it is
* recommended to use an atlas manager to avoid texture switching.
* If an atlas manager is given, the symbol is added to an atlas.
* By default no atlas manager is used.
* @type {ol.style.AtlasManager|undefined}
*/
olx.style.RegularShapeOptions.prototype.atlasManager;
/**
* @typedef {{color: (ol.Color|string|undefined),
* lineCap: (string|undefined),
@@ -6279,3 +6301,41 @@ olx.ViewState.prototype.resolution;
* @api
*/
olx.ViewState.prototype.rotation;
/**
* @typedef {{initialSize: (number|undefined),
* maxSize: (number|undefined),
* space: (number|undefined)}}
* @api
*/
olx.style.AtlasManagerOptions;
/**
* The size in pixels of the first atlas image. If no value is given the
* `ol.INITIAL_ATLAS_SIZE` compile-time constant will be used.
* @type {number|undefined}
* @api
*/
olx.style.AtlasManagerOptions.prototype.initialSize;
/**
* The maximum size in pixels of atlas images. If no value is given then
* the `ol.MAX_ATLAS_SIZE` compile-time constant will be used. And if
* `ol.MAX_ATLAS_SIZE` is set to `-1` (the default) then
* `ol.WEBGL_MAX_TEXTURE_SIZE` will used if WebGL is supported. Otherwise
* 2048 is used.
* @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;

View File

@@ -119,21 +119,32 @@ ol.has.MSPOINTER = !!(goog.global.navigator.msPointerEnabled);
* @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;
var textureSize;
var /** @type {Array.<string>} */ extensions = [];
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));
extensions = gl.getSupportedExtensions();
}
})();
} catch (e) {}
}
ol.has.WEBGL = hasWebGL;
ol.WEBGL_EXTENSIONS = extensions;
ol.WEBGL_MAX_TEXTURE_SIZE = textureSize;
}
})();

View File

@@ -152,6 +152,13 @@ ol.ENABLE_WEBGL = true;
ol.LEGACY_IE_SUPPORT = false;
/**
* @define {number} The size in pixels of the first atlas image. Default is
* `256`.
*/
ol.INITIAL_ATLAS_SIZE = 256;
/**
* The page is loaded using HTTPS.
* @const
@@ -175,6 +182,14 @@ ol.IS_LEGACY_IE = goog.userAgent.IE &&
ol.KEYBOARD_PAN_DURATION = 100;
/**
* @define {number} The maximum size in pixels of atlas images. Default is
* `-1`, meaning it is not used (and `ol.ol.WEBGL_MAX_TEXTURE_SIZE` is
* used instead).
*/
ol.MAX_ATLAS_SIZE = -1;
/**
* @define {number} Maximum mouse wheel delta.
*/
@@ -219,6 +234,24 @@ ol.SIMPLIFY_TOLERANCE = 0.5;
ol.WEBGL_TEXTURE_CACHE_HIGH_WATER_MARK = 1024;
/**
* The maximum supported WebGL texture size in pixels. If WebGL is not
* supported, the value is set to `undefined`.
* @const
* @type {number|undefined}
* @api
*/
ol.WEBGL_MAX_TEXTURE_SIZE; // value is set in `ol.has`
/**
* List of supported WebGL extensions.
* @const
* @type {Array.<string>}
*/
ol.WEBGL_EXTENSIONS; // value is set in `ol.has`
/**
* @define {number} Zoom slider animation duration.
*/

View File

@@ -2011,7 +2011,7 @@ ol.render.canvas.ReplayGroup.prototype.forEachGeometryAtPixel = function(
/**
* @inheritDoc
* FIXME empty description for jsdoc
*/
ol.render.canvas.ReplayGroup.prototype.finish = function() {
var zKey;

View File

@@ -35,13 +35,6 @@ ol.render.IReplayGroup = function() {
};
/**
* FIXME empty description for jsdoc
*/
ol.render.IReplayGroup.prototype.finish = function() {
};
/**
* @param {number|undefined} zIndex Z index.
* @param {ol.render.ReplayType} replayType Replay type.

View File

@@ -5,8 +5,8 @@ goog.provide('ol.render.IVectorContext');
/**
* VectorContext interface. Currently implemented by
* {@link ol.render.canvas.Immediate}
* VectorContext interface. Implemented by
* {@link ol.render.canvas.Immediate} and {@link ol.render.webgl.Immediate}.
* @interface
*/
ol.render.IVectorContext = function() {
@@ -15,7 +15,7 @@ ol.render.IVectorContext = function() {
/**
* @param {number} zIndex Z index.
* @param {function(ol.render.canvas.Immediate)} callback Callback.
* @param {function(ol.render.IVectorContext)} callback Callback.
*/
ol.render.IVectorContext.prototype.drawAsync = function(zIndex, callback) {
};

View File

@@ -0,0 +1,46 @@
//! NAMESPACE=ol.render.webgl.imagereplay.shader.Color
//! CLASS=ol.render.webgl.imagereplay.shader.Color
//! COMMON
varying vec2 v_texCoord;
varying float v_opacity;
//! VERTEX
attribute vec2 a_position;
attribute vec2 a_texCoord;
attribute vec2 a_offsets;
attribute float a_opacity;
attribute float a_rotateWithView;
uniform mat4 u_projectionMatrix;
uniform mat4 u_offsetScaleMatrix;
uniform mat4 u_offsetRotateMatrix;
void main(void) {
mat4 offsetMatrix = u_offsetScaleMatrix;
if (a_rotateWithView == 1.0) {
offsetMatrix = u_offsetScaleMatrix * u_offsetRotateMatrix;
}
vec4 offsets = offsetMatrix * vec4(a_offsets, 0., 0.);
gl_Position = u_projectionMatrix * vec4(a_position, 0., 1.) + offsets;
v_texCoord = a_texCoord;
v_opacity = a_opacity;
}
//! FRAGMENT
// @see https://svn.webkit.org/repository/webkit/trunk/Source/WebCore/platform/graphics/filters/skia/SkiaImageFilterBuilder.cpp
uniform mat4 u_colorMatrix;
uniform float u_opacity;
uniform sampler2D u_image;
void main(void) {
vec4 texColor = texture2D(u_image, v_texCoord);
float alpha = texColor.a * v_opacity * u_opacity;
if (alpha == 0.0) {
discard;
}
gl_FragColor.a = alpha;
gl_FragColor.rgb = (u_colorMatrix * vec4(texColor.rgb, 1.)).rgb;
}

View File

@@ -0,0 +1,153 @@
// This file is automatically generated, do not edit
goog.provide('ol.render.webgl.imagereplay.shader.Color');
goog.require('ol.webgl.shader');
/**
* @constructor
* @extends {ol.webgl.shader.Fragment}
* @struct
*/
ol.render.webgl.imagereplay.shader.ColorFragment = function() {
goog.base(this, ol.render.webgl.imagereplay.shader.ColorFragment.SOURCE);
};
goog.inherits(ol.render.webgl.imagereplay.shader.ColorFragment, ol.webgl.shader.Fragment);
goog.addSingletonGetter(ol.render.webgl.imagereplay.shader.ColorFragment);
/**
* @const
* @type {string}
*/
ol.render.webgl.imagereplay.shader.ColorFragment.DEBUG_SOURCE = 'precision mediump float;\nvarying vec2 v_texCoord;\nvarying float v_opacity;\n\n// @see https://svn.webkit.org/repository/webkit/trunk/Source/WebCore/platform/graphics/filters/skia/SkiaImageFilterBuilder.cpp\nuniform mat4 u_colorMatrix;\nuniform float u_opacity;\nuniform sampler2D u_image;\n\nvoid main(void) {\n vec4 texColor = texture2D(u_image, v_texCoord);\n float alpha = texColor.a * v_opacity * u_opacity;\n if (alpha == 0.0) {\n discard;\n }\n gl_FragColor.a = alpha;\n gl_FragColor.rgb = (u_colorMatrix * vec4(texColor.rgb, 1.)).rgb;\n}\n';
/**
* @const
* @type {string}
*/
ol.render.webgl.imagereplay.shader.ColorFragment.OPTIMIZED_SOURCE = 'precision mediump float;varying vec2 a;varying float b;uniform mat4 k;uniform float l;uniform sampler2D m;void main(void){vec4 texColor=texture2D(m,a);float alpha=texColor.a*b*l;if(alpha==0.0){discard;}gl_FragColor.a=alpha;gl_FragColor.rgb=(k*vec4(texColor.rgb,1.)).rgb;}';
/**
* @const
* @type {string}
*/
ol.render.webgl.imagereplay.shader.ColorFragment.SOURCE = goog.DEBUG ?
ol.render.webgl.imagereplay.shader.ColorFragment.DEBUG_SOURCE :
ol.render.webgl.imagereplay.shader.ColorFragment.OPTIMIZED_SOURCE;
/**
* @constructor
* @extends {ol.webgl.shader.Vertex}
* @struct
*/
ol.render.webgl.imagereplay.shader.ColorVertex = function() {
goog.base(this, ol.render.webgl.imagereplay.shader.ColorVertex.SOURCE);
};
goog.inherits(ol.render.webgl.imagereplay.shader.ColorVertex, ol.webgl.shader.Vertex);
goog.addSingletonGetter(ol.render.webgl.imagereplay.shader.ColorVertex);
/**
* @const
* @type {string}
*/
ol.render.webgl.imagereplay.shader.ColorVertex.DEBUG_SOURCE = 'varying vec2 v_texCoord;\nvarying float v_opacity;\n\nattribute vec2 a_position;\nattribute vec2 a_texCoord;\nattribute vec2 a_offsets;\nattribute float a_opacity;\nattribute float a_rotateWithView;\n\nuniform mat4 u_projectionMatrix;\nuniform mat4 u_offsetScaleMatrix;\nuniform mat4 u_offsetRotateMatrix;\n\nvoid main(void) {\n mat4 offsetMatrix = u_offsetScaleMatrix;\n if (a_rotateWithView == 1.0) {\n offsetMatrix = u_offsetScaleMatrix * u_offsetRotateMatrix;\n }\n vec4 offsets = offsetMatrix * vec4(a_offsets, 0., 0.);\n gl_Position = u_projectionMatrix * vec4(a_position, 0., 1.) + offsets;\n v_texCoord = a_texCoord;\n v_opacity = a_opacity;\n}\n\n\n';
/**
* @const
* @type {string}
*/
ol.render.webgl.imagereplay.shader.ColorVertex.OPTIMIZED_SOURCE = 'varying vec2 a;varying float b;attribute vec2 c;attribute vec2 d;attribute vec2 e;attribute float f;attribute float g;uniform mat4 h;uniform mat4 i;uniform mat4 j;void main(void){mat4 offsetMatrix=i;if(g==1.0){offsetMatrix=i*j;}vec4 offsets=offsetMatrix*vec4(e,0.,0.);gl_Position=h*vec4(c,0.,1.)+offsets;a=d;b=f;}';
/**
* @const
* @type {string}
*/
ol.render.webgl.imagereplay.shader.ColorVertex.SOURCE = goog.DEBUG ?
ol.render.webgl.imagereplay.shader.ColorVertex.DEBUG_SOURCE :
ol.render.webgl.imagereplay.shader.ColorVertex.OPTIMIZED_SOURCE;
/**
* @constructor
* @param {WebGLRenderingContext} gl GL.
* @param {WebGLProgram} program Program.
* @struct
*/
ol.render.webgl.imagereplay.shader.Color.Locations = function(gl, program) {
/**
* @type {WebGLUniformLocation}
*/
this.u_colorMatrix = gl.getUniformLocation(
program, goog.DEBUG ? 'u_colorMatrix' : 'k');
/**
* @type {WebGLUniformLocation}
*/
this.u_image = gl.getUniformLocation(
program, goog.DEBUG ? 'u_image' : 'm');
/**
* @type {WebGLUniformLocation}
*/
this.u_offsetRotateMatrix = gl.getUniformLocation(
program, goog.DEBUG ? 'u_offsetRotateMatrix' : 'j');
/**
* @type {WebGLUniformLocation}
*/
this.u_offsetScaleMatrix = gl.getUniformLocation(
program, goog.DEBUG ? 'u_offsetScaleMatrix' : 'i');
/**
* @type {WebGLUniformLocation}
*/
this.u_opacity = gl.getUniformLocation(
program, goog.DEBUG ? 'u_opacity' : 'l');
/**
* @type {WebGLUniformLocation}
*/
this.u_projectionMatrix = gl.getUniformLocation(
program, goog.DEBUG ? 'u_projectionMatrix' : 'h');
/**
* @type {number}
*/
this.a_offsets = gl.getAttribLocation(
program, goog.DEBUG ? 'a_offsets' : 'e');
/**
* @type {number}
*/
this.a_opacity = gl.getAttribLocation(
program, goog.DEBUG ? 'a_opacity' : 'f');
/**
* @type {number}
*/
this.a_position = gl.getAttribLocation(
program, goog.DEBUG ? 'a_position' : 'c');
/**
* @type {number}
*/
this.a_rotateWithView = gl.getAttribLocation(
program, goog.DEBUG ? 'a_rotateWithView' : 'g');
/**
* @type {number}
*/
this.a_texCoord = gl.getAttribLocation(
program, goog.DEBUG ? 'a_texCoord' : 'd');
};

View File

@@ -0,0 +1,44 @@
//! NAMESPACE=ol.render.webgl.imagereplay.shader.Default
//! CLASS=ol.render.webgl.imagereplay.shader.Default
//! COMMON
varying vec2 v_texCoord;
varying float v_opacity;
//! VERTEX
attribute vec2 a_position;
attribute vec2 a_texCoord;
attribute vec2 a_offsets;
attribute float a_opacity;
attribute float a_rotateWithView;
uniform mat4 u_projectionMatrix;
uniform mat4 u_offsetScaleMatrix;
uniform mat4 u_offsetRotateMatrix;
void main(void) {
mat4 offsetMatrix = u_offsetScaleMatrix;
if (a_rotateWithView == 1.0) {
offsetMatrix = u_offsetScaleMatrix * u_offsetRotateMatrix;
}
vec4 offsets = offsetMatrix * vec4(a_offsets, 0., 0.);
gl_Position = u_projectionMatrix * vec4(a_position, 0., 1.) + offsets;
v_texCoord = a_texCoord;
v_opacity = a_opacity;
}
//! FRAGMENT
uniform float u_opacity;
uniform sampler2D u_image;
void main(void) {
vec4 texColor = texture2D(u_image, v_texCoord);
gl_FragColor.rgb = texColor.rgb;
float alpha = texColor.a * v_opacity * u_opacity;
if (alpha == 0.0) {
discard;
}
gl_FragColor.a = alpha;
}

View File

@@ -0,0 +1,147 @@
// This file is automatically generated, do not edit
goog.provide('ol.render.webgl.imagereplay.shader.Default');
goog.require('ol.webgl.shader');
/**
* @constructor
* @extends {ol.webgl.shader.Fragment}
* @struct
*/
ol.render.webgl.imagereplay.shader.DefaultFragment = function() {
goog.base(this, ol.render.webgl.imagereplay.shader.DefaultFragment.SOURCE);
};
goog.inherits(ol.render.webgl.imagereplay.shader.DefaultFragment, ol.webgl.shader.Fragment);
goog.addSingletonGetter(ol.render.webgl.imagereplay.shader.DefaultFragment);
/**
* @const
* @type {string}
*/
ol.render.webgl.imagereplay.shader.DefaultFragment.DEBUG_SOURCE = 'precision mediump float;\nvarying vec2 v_texCoord;\nvarying float v_opacity;\n\nuniform float u_opacity;\nuniform sampler2D u_image;\n\nvoid main(void) {\n vec4 texColor = texture2D(u_image, v_texCoord);\n gl_FragColor.rgb = texColor.rgb;\n float alpha = texColor.a * v_opacity * u_opacity;\n if (alpha == 0.0) {\n discard;\n }\n gl_FragColor.a = alpha;\n}\n';
/**
* @const
* @type {string}
*/
ol.render.webgl.imagereplay.shader.DefaultFragment.OPTIMIZED_SOURCE = 'precision mediump float;varying vec2 a;varying float b;uniform float k;uniform sampler2D l;void main(void){vec4 texColor=texture2D(l,a);gl_FragColor.rgb=texColor.rgb;float alpha=texColor.a*b*k;if(alpha==0.0){discard;}gl_FragColor.a=alpha;}';
/**
* @const
* @type {string}
*/
ol.render.webgl.imagereplay.shader.DefaultFragment.SOURCE = goog.DEBUG ?
ol.render.webgl.imagereplay.shader.DefaultFragment.DEBUG_SOURCE :
ol.render.webgl.imagereplay.shader.DefaultFragment.OPTIMIZED_SOURCE;
/**
* @constructor
* @extends {ol.webgl.shader.Vertex}
* @struct
*/
ol.render.webgl.imagereplay.shader.DefaultVertex = function() {
goog.base(this, ol.render.webgl.imagereplay.shader.DefaultVertex.SOURCE);
};
goog.inherits(ol.render.webgl.imagereplay.shader.DefaultVertex, ol.webgl.shader.Vertex);
goog.addSingletonGetter(ol.render.webgl.imagereplay.shader.DefaultVertex);
/**
* @const
* @type {string}
*/
ol.render.webgl.imagereplay.shader.DefaultVertex.DEBUG_SOURCE = 'varying vec2 v_texCoord;\nvarying float v_opacity;\n\nattribute vec2 a_position;\nattribute vec2 a_texCoord;\nattribute vec2 a_offsets;\nattribute float a_opacity;\nattribute float a_rotateWithView;\n\nuniform mat4 u_projectionMatrix;\nuniform mat4 u_offsetScaleMatrix;\nuniform mat4 u_offsetRotateMatrix;\n\nvoid main(void) {\n mat4 offsetMatrix = u_offsetScaleMatrix;\n if (a_rotateWithView == 1.0) {\n offsetMatrix = u_offsetScaleMatrix * u_offsetRotateMatrix;\n }\n vec4 offsets = offsetMatrix * vec4(a_offsets, 0., 0.);\n gl_Position = u_projectionMatrix * vec4(a_position, 0., 1.) + offsets;\n v_texCoord = a_texCoord;\n v_opacity = a_opacity;\n}\n\n\n';
/**
* @const
* @type {string}
*/
ol.render.webgl.imagereplay.shader.DefaultVertex.OPTIMIZED_SOURCE = 'varying vec2 a;varying float b;attribute vec2 c;attribute vec2 d;attribute vec2 e;attribute float f;attribute float g;uniform mat4 h;uniform mat4 i;uniform mat4 j;void main(void){mat4 offsetMatrix=i;if(g==1.0){offsetMatrix=i*j;}vec4 offsets=offsetMatrix*vec4(e,0.,0.);gl_Position=h*vec4(c,0.,1.)+offsets;a=d;b=f;}';
/**
* @const
* @type {string}
*/
ol.render.webgl.imagereplay.shader.DefaultVertex.SOURCE = goog.DEBUG ?
ol.render.webgl.imagereplay.shader.DefaultVertex.DEBUG_SOURCE :
ol.render.webgl.imagereplay.shader.DefaultVertex.OPTIMIZED_SOURCE;
/**
* @constructor
* @param {WebGLRenderingContext} gl GL.
* @param {WebGLProgram} program Program.
* @struct
*/
ol.render.webgl.imagereplay.shader.Default.Locations = function(gl, program) {
/**
* @type {WebGLUniformLocation}
*/
this.u_image = gl.getUniformLocation(
program, goog.DEBUG ? 'u_image' : 'l');
/**
* @type {WebGLUniformLocation}
*/
this.u_offsetRotateMatrix = gl.getUniformLocation(
program, goog.DEBUG ? 'u_offsetRotateMatrix' : 'j');
/**
* @type {WebGLUniformLocation}
*/
this.u_offsetScaleMatrix = gl.getUniformLocation(
program, goog.DEBUG ? 'u_offsetScaleMatrix' : 'i');
/**
* @type {WebGLUniformLocation}
*/
this.u_opacity = gl.getUniformLocation(
program, goog.DEBUG ? 'u_opacity' : 'k');
/**
* @type {WebGLUniformLocation}
*/
this.u_projectionMatrix = gl.getUniformLocation(
program, goog.DEBUG ? 'u_projectionMatrix' : 'h');
/**
* @type {number}
*/
this.a_offsets = gl.getAttribLocation(
program, goog.DEBUG ? 'a_offsets' : 'e');
/**
* @type {number}
*/
this.a_opacity = gl.getAttribLocation(
program, goog.DEBUG ? 'a_opacity' : 'f');
/**
* @type {number}
*/
this.a_position = gl.getAttribLocation(
program, goog.DEBUG ? 'a_position' : 'c');
/**
* @type {number}
*/
this.a_rotateWithView = gl.getAttribLocation(
program, goog.DEBUG ? 'a_rotateWithView' : 'g');
/**
* @type {number}
*/
this.a_texCoord = gl.getAttribLocation(
program, goog.DEBUG ? 'a_texCoord' : 'd');
};

View File

@@ -1,4 +1,8 @@
goog.provide('ol.render.webgl.Immediate');
goog.require('goog.array');
goog.require('goog.object');
goog.require('ol.extent');
goog.require('ol.render.webgl.ReplayGroup');
@@ -6,22 +10,103 @@ goog.provide('ol.render.webgl.Immediate');
* @constructor
* @implements {ol.render.IVectorContext}
* @param {ol.webgl.Context} context Context.
* @param {ol.Coordinate} center Center.
* @param {number} resolution Resolution.
* @param {number} rotation Rotation.
* @param {ol.Size} size Size.
* @param {ol.Extent} extent Extent.
* @param {number} pixelRatio Pixel ratio.
* @struct
*/
ol.render.webgl.Immediate = function(context, pixelRatio) {
ol.render.webgl.Immediate = function(context,
center, resolution, rotation, size, extent, pixelRatio) {
/**
* @private
*/
this.context_ = context;
/**
* @private
*/
this.center_ = center;
/**
* @private
*/
this.extent_ = extent;
/**
* @private
*/
this.pixelRatio_ = pixelRatio;
/**
* @private
*/
this.size_ = size;
/**
* @private
*/
this.rotation_ = rotation;
/**
* @private
*/
this.resolution_ = resolution;
/**
* @private
* @type {ol.style.Image}
*/
this.imageStyle_ = null;
/**
* @private
* @type {Object.<string,
* Array.<function(ol.render.webgl.Immediate)>>}
*/
this.callbacksByZIndex_ = {};
};
/**
* @inheritDoc
* FIXME: empty description for jsdoc
*/
ol.render.webgl.Immediate.prototype.flush = function() {
/** @type {Array.<number>} */
var zs = goog.array.map(goog.object.getKeys(this.callbacksByZIndex_), Number);
goog.array.sort(zs);
var i, ii, callbacks, j, jj;
for (i = 0, ii = zs.length; i < ii; ++i) {
callbacks = this.callbacksByZIndex_[zs[i].toString()];
for (j = 0, jj = callbacks.length; j < jj; ++j) {
callbacks[j](this);
}
}
};
/**
* @param {number} zIndex Z index.
* @param {function(ol.render.webgl.Immediate)} callback Callback.
* @api
*/
ol.render.webgl.Immediate.prototype.drawAsync = function(zIndex, callback) {
var zIndexKey = zIndex.toString();
var callbacks = this.callbacksByZIndex_[zIndexKey];
if (goog.isDef(callbacks)) {
callbacks.push(callback);
} else {
this.callbacksByZIndex_[zIndexKey] = [callback];
}
};
/**
* @inheritDoc
* @api
*/
ol.render.webgl.Immediate.prototype.drawCircleGeometry =
function(circleGeometry, data) {
@@ -30,29 +115,83 @@ ol.render.webgl.Immediate.prototype.drawCircleGeometry =
/**
* @inheritDoc
* @api
*/
ol.render.webgl.Immediate.prototype.drawFeature = function(feature, style) {
var geometry = feature.getGeometry();
if (!goog.isDefAndNotNull(geometry) ||
!ol.extent.intersects(this.extent_, geometry.getExtent())) {
return;
}
var zIndex = style.getZIndex();
if (!goog.isDef(zIndex)) {
zIndex = 0;
}
this.drawAsync(zIndex, function(render) {
render.setFillStrokeStyle(style.getFill(), style.getStroke());
render.setImageStyle(style.getImage());
render.setTextStyle(style.getText());
var type = geometry.getType();
var renderGeometry = ol.render.webgl.Immediate.GEOMETRY_RENDERERS_[type];
// Do not assert since all kinds of geometries are not handled yet.
// In spite, render what we support.
if (renderGeometry) {
renderGeometry.call(render, geometry, null);
}
});
};
/**
* @inheritDoc
* @api
*/
ol.render.webgl.Immediate.prototype.drawGeometryCollectionGeometry =
function(geometryCollectionGeometry, data) {
var geometries = geometryCollectionGeometry.getGeometriesArray();
var renderers = ol.render.webgl.Immediate.GEOMETRY_RENDERERS_;
var i, ii;
for (i = 0, ii = geometries.length; i < ii; ++i) {
var geometry = geometries[i];
var geometryRenderer = renderers[geometry.getType()];
// Do not assert since all kinds of geometries are not handled yet.
// In order to support hierarchies, delegate instead what we can to
// valid renderers.
if (geometryRenderer) {
geometryRenderer.call(this, geometry, data);
}
}
};
/**
* @inheritDoc
* @api
*/
ol.render.webgl.Immediate.prototype.drawPointGeometry =
function(pointGeometry, data) {
var context = this.context_;
var replayGroup = new ol.render.webgl.ReplayGroup(1, this.extent_);
var replay = replayGroup.getReplay(0, ol.render.ReplayType.IMAGE);
replay.setImageStyle(this.imageStyle_);
replay.drawPointGeometry(pointGeometry, data);
replay.finish(context);
// default colors
var opacity = 1;
var brightness = 0;
var contrast = 1;
var hue = 0;
var saturation = 1;
replay.replay(this.context_, this.center_, this.resolution_, this.rotation_,
this.size_, this.extent_, this.pixelRatio_, opacity, brightness,
contrast, hue, saturation, {});
replay.getDeleteResourcesFunction(context)();
};
/**
* @inheritDoc
* @api
*/
ol.render.webgl.Immediate.prototype.drawLineStringGeometry =
function(lineStringGeometry, data) {
@@ -61,6 +200,7 @@ ol.render.webgl.Immediate.prototype.drawLineStringGeometry =
/**
* @inheritDoc
* @api
*/
ol.render.webgl.Immediate.prototype.drawMultiLineStringGeometry =
function(multiLineStringGeometry, data) {
@@ -69,14 +209,32 @@ ol.render.webgl.Immediate.prototype.drawMultiLineStringGeometry =
/**
* @inheritDoc
* @api
*/
ol.render.webgl.Immediate.prototype.drawMultiPointGeometry =
function(multiPointGeometry, data) {
var context = this.context_;
var replayGroup = new ol.render.webgl.ReplayGroup(1, this.extent_);
var replay = replayGroup.getReplay(0, ol.render.ReplayType.IMAGE);
replay.setImageStyle(this.imageStyle_);
replay.drawMultiPointGeometry(multiPointGeometry, data);
replay.finish(context);
// default colors
var opacity = 1;
var brightness = 0;
var contrast = 1;
var hue = 0;
var saturation = 1;
replay.replay(this.context_, this.center_, this.resolution_, this.rotation_,
this.size_, this.extent_, this.pixelRatio_, opacity, brightness,
contrast, hue, saturation, {});
replay.getDeleteResourcesFunction(context)();
};
/**
* @inheritDoc
* @api
*/
ol.render.webgl.Immediate.prototype.drawMultiPolygonGeometry =
function(multiPolygonGeometry, data) {
@@ -85,6 +243,7 @@ ol.render.webgl.Immediate.prototype.drawMultiPolygonGeometry =
/**
* @inheritDoc
* @api
*/
ol.render.webgl.Immediate.prototype.drawPolygonGeometry =
function(polygonGeometry, data) {
@@ -93,6 +252,7 @@ ol.render.webgl.Immediate.prototype.drawPolygonGeometry =
/**
* @inheritDoc
* @api
*/
ol.render.webgl.Immediate.prototype.drawText =
function(flatCoordinates, offset, end, stride, geometry, data) {
@@ -101,6 +261,7 @@ ol.render.webgl.Immediate.prototype.drawText =
/**
* @inheritDoc
* @api
*/
ol.render.webgl.Immediate.prototype.setFillStrokeStyle =
function(fillStyle, strokeStyle) {
@@ -109,13 +270,31 @@ ol.render.webgl.Immediate.prototype.setFillStrokeStyle =
/**
* @inheritDoc
* @api
*/
ol.render.webgl.Immediate.prototype.setImageStyle = function(imageStyle) {
this.imageStyle_ = imageStyle;
};
/**
* @inheritDoc
* @api
*/
ol.render.webgl.Immediate.prototype.setTextStyle = function(textStyle) {
};
/**
* @const
* @private
* @type {Object.<ol.geom.GeometryType,
* function(this: ol.render.webgl.Immediate, ol.geom.Geometry,
* Object)>}
*/
ol.render.webgl.Immediate.GEOMETRY_RENDERERS_ = {
'Point': ol.render.webgl.Immediate.prototype.drawPointGeometry,
'MultiPoint': ol.render.webgl.Immediate.prototype.drawMultiPointGeometry,
'GeometryCollection':
ol.render.webgl.Immediate.prototype.drawGeometryCollectionGeometry
};

View File

@@ -0,0 +1,841 @@
goog.provide('ol.render.webgl.ImageReplay');
goog.provide('ol.render.webgl.ReplayGroup');
goog.require('goog.array');
goog.require('goog.asserts');
goog.require('goog.functions');
goog.require('goog.object');
goog.require('goog.vec.Mat4');
goog.require('ol.color.Matrix');
goog.require('ol.extent');
goog.require('ol.render.IReplayGroup');
goog.require('ol.render.webgl.imagereplay.shader.Color');
goog.require('ol.render.webgl.imagereplay.shader.Default');
goog.require('ol.vec.Mat4');
goog.require('ol.webgl.Buffer');
/**
* @constructor
* @implements {ol.render.IVectorContext}
* @param {number} tolerance Tolerance.
* @param {ol.Extent} maxExtent Max extent.
* @protected
* @struct
*/
ol.render.webgl.ImageReplay = function(tolerance, maxExtent) {
/**
* @type {number|undefined}
* @private
*/
this.anchorX_ = undefined;
/**
* @type {number|undefined}
* @private
*/
this.anchorY_ = undefined;
/**
* @private
* @type {ol.color.Matrix}
*/
this.colorMatrix_ = new ol.color.Matrix();
/**
* The origin of the coordinate system for the point coordinates sent to
* the GPU. To eliminate jitter caused by precision problems in the GPU
* we use the "Rendering Relative to Eye" technique described in the "3D
* Engine Design for Virtual Globes" book.
* @private
* @type {ol.Coordinate}
*/
this.origin_ = ol.extent.getCenter(maxExtent);
/**
* @type {ol.Extent}
* @private
*/
this.extent_ = ol.extent.createEmpty();
/**
* @type {Array.<number>}
* @private
*/
this.groupIndices_ = [];
/**
* @type {number|undefined}
* @private
*/
this.height_ = undefined;
/**
* @type {Array.<HTMLCanvasElement|HTMLImageElement|HTMLVideoElement>}
* @private
*/
this.images_ = [];
/**
* @type {number|undefined}
* @private
*/
this.imageHeight_ = undefined;
/**
* @type {number|undefined}
* @private
*/
this.imageWidth_ = undefined;
/**
* @type {Array.<number>}
* @private
*/
this.indices_ = [];
/**
* @type {ol.webgl.Buffer}
* @private
*/
this.indicesBuffer_ = null;
/**
* @private
* @type {ol.render.webgl.imagereplay.shader.Color.Locations}
*/
this.colorLocations_ = null;
/**
* @private
* @type {ol.render.webgl.imagereplay.shader.Default.Locations}
*/
this.defaultLocations_ = null;
/**
* @private
* @type {number|undefined}
*/
this.opacity_ = undefined;
/**
* @type {!goog.vec.Mat4.Number}
* @private
*/
this.offsetRotateMatrix_ = goog.vec.Mat4.createNumberIdentity();
/**
* @type {!goog.vec.Mat4.Number}
* @private
*/
this.offsetScaleMatrix_ = goog.vec.Mat4.createNumberIdentity();
/**
* @type {number|undefined}
* @private
*/
this.originX_ = undefined;
/**
* @type {number|undefined}
* @private
*/
this.originY_ = undefined;
/**
* @type {!goog.vec.Mat4.Number}
* @private
*/
this.projectionMatrix_ = goog.vec.Mat4.createNumberIdentity();
/**
* @private
* @type {boolean|undefined}
*/
this.rotateWithView_ = undefined;
/**
* @private
* @type {number|undefined}
*/
this.rotation_ = undefined;
/**
* @private
* @type {number|undefined}
*/
this.scale_ = undefined;
/**
* @type {Array.<WebGLTexture>}
* @private
*/
this.textures_ = [];
/**
* @type {Array.<number>}
* @private
*/
this.vertices_ = [];
/**
* @type {ol.webgl.Buffer}
* @private
*/
this.verticesBuffer_ = null;
/**
* @type {number|undefined}
* @private
*/
this.width_ = undefined;
};
/**
* @param {ol.webgl.Context} context WebGL context.
* @return {function()} Delete resources function.
*/
ol.render.webgl.ImageReplay.prototype.getDeleteResourcesFunction =
function(context) {
// We only delete our stuff here. The shaders and the program may
// be used by other ImageReplay instances (for other layers). And
// they will be deleted when disposing of the ol.webgl.Context
// object.
goog.asserts.assert(!goog.isNull(this.verticesBuffer_));
goog.asserts.assert(!goog.isNull(this.indicesBuffer_));
var verticesBuffer = this.verticesBuffer_;
var indicesBuffer = this.indicesBuffer_;
var textures = this.textures_;
var gl = context.getGL();
return function() {
if (!gl.isContextLost()) {
var i, ii;
for (i = 0, ii = textures.length; i < ii; ++i) {
gl.deleteTexture(textures[i]);
}
}
context.deleteBuffer(verticesBuffer);
context.deleteBuffer(indicesBuffer);
};
};
/**
* @inheritDoc
*/
ol.render.webgl.ImageReplay.prototype.drawAsync = goog.abstractMethod;
/**
* @param {Array.<number>} flatCoordinates Flat coordinates.
* @param {number} offset Offset.
* @param {number} end End.
* @param {number} stride Stride.
* @return {number} My end.
* @private
*/
ol.render.webgl.ImageReplay.prototype.drawCoordinates_ =
function(flatCoordinates, offset, end, stride) {
goog.asserts.assert(goog.isDef(this.anchorX_));
goog.asserts.assert(goog.isDef(this.anchorY_));
goog.asserts.assert(goog.isDef(this.height_));
goog.asserts.assert(goog.isDef(this.imageHeight_));
goog.asserts.assert(goog.isDef(this.imageWidth_));
goog.asserts.assert(goog.isDef(this.opacity_));
goog.asserts.assert(goog.isDef(this.originX_));
goog.asserts.assert(goog.isDef(this.originY_));
goog.asserts.assert(goog.isDef(this.rotateWithView_));
goog.asserts.assert(goog.isDef(this.rotation_));
goog.asserts.assert(goog.isDef(this.scale_));
goog.asserts.assert(goog.isDef(this.width_));
var anchorX = this.anchorX_;
var anchorY = this.anchorY_;
var height = this.height_;
var imageHeight = this.imageHeight_;
var imageWidth = this.imageWidth_;
var opacity = this.opacity_;
var originX = this.originX_;
var originY = this.originY_;
var rotateWithView = this.rotateWithView_ ? 1.0 : 0.0;
var rotation = this.rotation_;
var scale = this.scale_;
var width = this.width_;
var cos = Math.cos(rotation);
var sin = Math.sin(rotation);
var numIndices = this.indices_.length;
var numVertices = this.vertices_.length;
var i, n, offsetX, offsetY, x, y;
for (i = offset; i < end; i += stride) {
x = flatCoordinates[i] - this.origin_[0];
y = flatCoordinates[i + 1] - this.origin_[1];
// There are 4 vertices per [x, y] point, one for each corner of the
// rectangle we're going to draw. We'd use 1 vertex per [x, y] point if
// WebGL supported Geometry Shaders (which can emit new vertices), but that
// is not currently the case.
//
// And each vertex includes 8 values: the x and y coordinates, the x and
// y offsets used to calculate the position of the corner, the u and
// v texture coordinates for the corner, the opacity, and whether the
// the image should be rotated with the view (rotateWithView).
n = numVertices / 8;
// bottom-left corner
offsetX = -scale * anchorX;
offsetY = -scale * (height - anchorY);
this.vertices_[numVertices++] = x;
this.vertices_[numVertices++] = y;
this.vertices_[numVertices++] = offsetX * cos - offsetY * sin;
this.vertices_[numVertices++] = offsetX * sin + offsetY * cos;
this.vertices_[numVertices++] = originX / imageWidth;
this.vertices_[numVertices++] = (originY + height) / imageHeight;
this.vertices_[numVertices++] = opacity;
this.vertices_[numVertices++] = rotateWithView;
// bottom-right corner
offsetX = scale * (width - anchorX);
offsetY = -scale * (height - anchorY);
this.vertices_[numVertices++] = x;
this.vertices_[numVertices++] = y;
this.vertices_[numVertices++] = offsetX * cos - offsetY * sin;
this.vertices_[numVertices++] = offsetX * sin + offsetY * cos;
this.vertices_[numVertices++] = (originX + width) / imageWidth;
this.vertices_[numVertices++] = (originY + height) / imageHeight;
this.vertices_[numVertices++] = opacity;
this.vertices_[numVertices++] = rotateWithView;
// top-right corner
offsetX = scale * (width - anchorX);
offsetY = scale * anchorY;
this.vertices_[numVertices++] = x;
this.vertices_[numVertices++] = y;
this.vertices_[numVertices++] = offsetX * cos - offsetY * sin;
this.vertices_[numVertices++] = offsetX * sin + offsetY * cos;
this.vertices_[numVertices++] = (originX + width) / imageWidth;
this.vertices_[numVertices++] = originY / imageHeight;
this.vertices_[numVertices++] = opacity;
this.vertices_[numVertices++] = rotateWithView;
// top-left corner
offsetX = -scale * anchorX;
offsetY = scale * anchorY;
this.vertices_[numVertices++] = x;
this.vertices_[numVertices++] = y;
this.vertices_[numVertices++] = offsetX * cos - offsetY * sin;
this.vertices_[numVertices++] = offsetX * sin + offsetY * cos;
this.vertices_[numVertices++] = originX / imageWidth;
this.vertices_[numVertices++] = originY / imageHeight;
this.vertices_[numVertices++] = opacity;
this.vertices_[numVertices++] = rotateWithView;
this.indices_[numIndices++] = n;
this.indices_[numIndices++] = n + 1;
this.indices_[numIndices++] = n + 2;
this.indices_[numIndices++] = n;
this.indices_[numIndices++] = n + 2;
this.indices_[numIndices++] = n + 3;
}
return numVertices;
};
/**
* @inheritDoc
*/
ol.render.webgl.ImageReplay.prototype.drawCircleGeometry = goog.abstractMethod;
/**
* @inheritDoc
*/
ol.render.webgl.ImageReplay.prototype.drawFeature = goog.abstractMethod;
/**
* @inheritDoc
*/
ol.render.webgl.ImageReplay.prototype.drawGeometryCollectionGeometry =
goog.abstractMethod;
/**
* @inheritDoc
*/
ol.render.webgl.ImageReplay.prototype.drawLineStringGeometry =
goog.abstractMethod;
/**
* @inheritDoc
*/
ol.render.webgl.ImageReplay.prototype.drawMultiLineStringGeometry =
goog.abstractMethod;
/**
* @inheritDoc
*/
ol.render.webgl.ImageReplay.prototype.drawMultiPointGeometry =
function(multiPointGeometry, data) {
ol.extent.extend(this.extent_, multiPointGeometry.getExtent());
var flatCoordinates = multiPointGeometry.getFlatCoordinates();
var stride = multiPointGeometry.getStride();
this.drawCoordinates_(
flatCoordinates, 0, flatCoordinates.length, stride);
};
/**
* @inheritDoc
*/
ol.render.webgl.ImageReplay.prototype.drawMultiPolygonGeometry =
goog.abstractMethod;
/**
* @inheritDoc
*/
ol.render.webgl.ImageReplay.prototype.drawPointGeometry =
function(pointGeometry, data) {
ol.extent.extend(this.extent_, pointGeometry.getExtent());
var flatCoordinates = pointGeometry.getFlatCoordinates();
var stride = pointGeometry.getStride();
this.drawCoordinates_(
flatCoordinates, 0, flatCoordinates.length, stride);
};
/**
* @inheritDoc
*/
ol.render.webgl.ImageReplay.prototype.drawPolygonGeometry = goog.abstractMethod;
/**
* @inheritDoc
*/
ol.render.webgl.ImageReplay.prototype.drawText = goog.abstractMethod;
/**
* @param {ol.webgl.Context} context Context.
*/
ol.render.webgl.ImageReplay.prototype.finish = function(context) {
var gl = context.getGL();
this.groupIndices_.push(this.indices_.length);
goog.asserts.assert(this.images_.length == this.groupIndices_.length);
// create, bind, and populate the vertices buffer
this.verticesBuffer_ = new ol.webgl.Buffer(this.vertices_);
context.bindBuffer(goog.webgl.ARRAY_BUFFER, this.verticesBuffer_);
var indices = this.indices_;
var bits = context.hasOESElementIndexUint ? 32 : 16;
goog.asserts.assert(indices[indices.length - 1] < Math.pow(2, bits),
'Too large element index detected [%s] (OES_element_index_uint "%s")',
indices[indices.length - 1], context.hasOESElementIndexUint);
// create, bind, and populate the indices buffer
this.indicesBuffer_ = new ol.webgl.Buffer(indices);
context.bindBuffer(goog.webgl.ELEMENT_ARRAY_BUFFER, this.indicesBuffer_);
goog.asserts.assert(this.textures_.length === 0);
// create textures
var texture, image, uid;
/** @type {Object.<string, WebGLTexture>} */
var texturePerImage = {};
var i;
var ii = this.images_.length;
for (i = 0; i < ii; ++i) {
image = this.images_[i];
uid = goog.getUid(image).toString();
if (goog.object.containsKey(texturePerImage, uid)) {
texture = goog.object.get(texturePerImage, uid);
} else {
texture = gl.createTexture();
gl.bindTexture(goog.webgl.TEXTURE_2D, texture);
gl.texParameteri(goog.webgl.TEXTURE_2D,
goog.webgl.TEXTURE_WRAP_S, goog.webgl.CLAMP_TO_EDGE);
gl.texParameteri(goog.webgl.TEXTURE_2D,
goog.webgl.TEXTURE_WRAP_T, goog.webgl.CLAMP_TO_EDGE);
gl.texParameteri(goog.webgl.TEXTURE_2D,
goog.webgl.TEXTURE_MIN_FILTER, goog.webgl.LINEAR);
gl.texParameteri(goog.webgl.TEXTURE_2D,
goog.webgl.TEXTURE_MAG_FILTER, goog.webgl.LINEAR);
gl.texImage2D(goog.webgl.TEXTURE_2D, 0, goog.webgl.RGBA, goog.webgl.RGBA,
goog.webgl.UNSIGNED_BYTE, image);
goog.object.set(texturePerImage, uid, texture);
}
this.textures_[i] = texture;
}
goog.asserts.assert(this.textures_.length == this.groupIndices_.length);
this.anchorX_ = undefined;
this.anchorY_ = undefined;
this.height_ = undefined;
this.images_ = null;
this.imageHeight_ = undefined;
this.imageWidth_ = undefined;
this.indices_ = null;
this.opacity_ = undefined;
this.originX_ = undefined;
this.originY_ = undefined;
this.rotateWithView_ = undefined;
this.rotation_ = undefined;
this.scale_ = undefined;
this.vertices_ = null;
this.width_ = undefined;
};
/**
* @return {ol.Extent} Extent.
*/
ol.render.webgl.ImageReplay.prototype.getExtent = function() {
return this.extent_;
};
/**
* @param {ol.webgl.Context} context Context.
* @param {ol.Coordinate} center Center.
* @param {number} resolution Resolution.
* @param {number} rotation Rotation.
* @param {ol.Size} size Size.
* @param {ol.Extent} extent Extent.
* @param {number} pixelRatio Pixel ratio.
* @param {number} opacity Global opacity.
* @param {number} brightness Global brightness.
* @param {number} contrast Global contrast.
* @param {number} hue Global hue.
* @param {number} saturation Global saturation.
* @param {Object} skippedFeaturesHash Ids of features to skip.
* @return {T|undefined} Callback result.
* @template T
*/
ol.render.webgl.ImageReplay.prototype.replay = function(context,
center, resolution, rotation, size, extent, pixelRatio,
opacity, brightness, contrast, hue, saturation, skippedFeaturesHash) {
var gl = context.getGL();
// bind the vertices buffer
goog.asserts.assert(!goog.isNull(this.verticesBuffer_));
context.bindBuffer(goog.webgl.ARRAY_BUFFER, this.verticesBuffer_);
// bind the indices buffer
goog.asserts.assert(!goog.isNull(this.indicesBuffer_));
context.bindBuffer(goog.webgl.ELEMENT_ARRAY_BUFFER, this.indicesBuffer_);
var useColor = brightness || contrast != 1 || hue || saturation != 1;
// get the program
var fragmentShader, vertexShader;
if (useColor) {
fragmentShader =
ol.render.webgl.imagereplay.shader.ColorFragment.getInstance();
vertexShader =
ol.render.webgl.imagereplay.shader.ColorVertex.getInstance();
} else {
fragmentShader =
ol.render.webgl.imagereplay.shader.DefaultFragment.getInstance();
vertexShader =
ol.render.webgl.imagereplay.shader.DefaultVertex.getInstance();
}
var program = context.getProgram(fragmentShader, vertexShader);
// get the locations
var locations;
if (useColor) {
if (goog.isNull(this.colorLocations_)) {
locations =
new ol.render.webgl.imagereplay.shader.Color.Locations(gl, program);
this.colorLocations_ = locations;
} else {
locations = this.colorLocations_;
}
} else {
if (goog.isNull(this.defaultLocations_)) {
locations =
new ol.render.webgl.imagereplay.shader.Default.Locations(gl, program);
this.defaultLocations_ = locations;
} else {
locations = this.defaultLocations_;
}
}
// use the program (FIXME: use the return value)
context.useProgram(program);
// enable the vertex attrib arrays
gl.enableVertexAttribArray(locations.a_position);
gl.vertexAttribPointer(locations.a_position, 2, goog.webgl.FLOAT,
false, 32, 0);
gl.enableVertexAttribArray(locations.a_offsets);
gl.vertexAttribPointer(locations.a_offsets, 2, goog.webgl.FLOAT,
false, 32, 8);
gl.enableVertexAttribArray(locations.a_texCoord);
gl.vertexAttribPointer(locations.a_texCoord, 2, goog.webgl.FLOAT,
false, 32, 16);
gl.enableVertexAttribArray(locations.a_opacity);
gl.vertexAttribPointer(locations.a_opacity, 1, goog.webgl.FLOAT,
false, 32, 24);
gl.enableVertexAttribArray(locations.a_rotateWithView);
gl.vertexAttribPointer(locations.a_rotateWithView, 1, goog.webgl.FLOAT,
false, 32, 28);
// set the "uniform" values
var projectionMatrix = this.projectionMatrix_;
ol.vec.Mat4.makeTransform2D(projectionMatrix,
0.0, 0.0,
2 / (resolution * size[0]),
2 / (resolution * size[1]),
-rotation,
-(center[0] - this.origin_[0]), -(center[1] - this.origin_[1]));
var offsetScaleMatrix = this.offsetScaleMatrix_;
goog.vec.Mat4.makeScale(offsetScaleMatrix, 2 / size[0], 2 / size[1], 1);
var offsetRotateMatrix = this.offsetRotateMatrix_;
goog.vec.Mat4.makeIdentity(offsetRotateMatrix);
if (rotation !== 0) {
goog.vec.Mat4.rotateZ(offsetRotateMatrix, -rotation);
}
gl.uniformMatrix4fv(locations.u_projectionMatrix, false, projectionMatrix);
gl.uniformMatrix4fv(locations.u_offsetScaleMatrix, false, offsetScaleMatrix);
gl.uniformMatrix4fv(locations.u_offsetRotateMatrix, false,
offsetRotateMatrix);
gl.uniform1f(locations.u_opacity, opacity);
if (useColor) {
gl.uniformMatrix4fv(locations.u_colorMatrix, false,
this.colorMatrix_.getMatrix(brightness, contrast, hue, saturation));
}
// draw!
goog.asserts.assert(this.textures_.length == this.groupIndices_.length);
var i, ii, start;
for (i = 0, ii = this.textures_.length, start = 0; i < ii; ++i) {
gl.bindTexture(goog.webgl.TEXTURE_2D, this.textures_[i]);
var end = this.groupIndices_[i];
var numItems = end - start;
var offsetInBytes = start * (context.hasOESElementIndexUint ? 4 : 2);
var elementType = context.hasOESElementIndexUint ?
goog.webgl.UNSIGNED_INT : goog.webgl.UNSIGNED_SHORT;
gl.drawElements(goog.webgl.TRIANGLES, numItems, elementType, offsetInBytes);
start = end;
}
// disable the vertex attrib arrays
gl.disableVertexAttribArray(locations.a_position);
gl.disableVertexAttribArray(locations.a_offsets);
gl.disableVertexAttribArray(locations.a_texCoord);
gl.disableVertexAttribArray(locations.a_opacity);
gl.disableVertexAttribArray(locations.a_rotateWithView);
};
/**
* @inheritDoc
*/
ol.render.webgl.ImageReplay.prototype.setFillStrokeStyle = goog.abstractMethod;
/**
* @inheritDoc
*/
ol.render.webgl.ImageReplay.prototype.setImageStyle = function(imageStyle) {
var anchor = imageStyle.getAnchor();
goog.asserts.assert(!goog.isNull(anchor));
var image = imageStyle.getImage(1);
goog.asserts.assert(!goog.isNull(image));
var imageSize = imageStyle.getImageSize();
goog.asserts.assert(!goog.isNull(imageSize));
var opacity = imageStyle.getOpacity();
goog.asserts.assert(goog.isDef(opacity));
var origin = imageStyle.getOrigin();
goog.asserts.assert(!goog.isNull(origin));
var rotateWithView = imageStyle.getRotateWithView();
goog.asserts.assert(goog.isDef(rotateWithView));
var rotation = imageStyle.getRotation();
goog.asserts.assert(goog.isDef(rotation));
var size = imageStyle.getSize();
goog.asserts.assert(!goog.isNull(size));
var scale = imageStyle.getScale();
goog.asserts.assert(goog.isDef(scale));
if (this.images_.length === 0) {
this.images_.push(image);
} else {
var currentImage = this.images_[this.images_.length - 1];
if (goog.getUid(currentImage) != goog.getUid(image)) {
this.groupIndices_.push(this.indices_.length);
goog.asserts.assert(this.groupIndices_.length == this.images_.length);
this.images_.push(image);
}
}
this.anchorX_ = anchor[0];
this.anchorY_ = anchor[1];
this.height_ = size[1];
this.imageHeight_ = imageSize[1];
this.imageWidth_ = imageSize[0];
this.opacity_ = opacity;
this.originX_ = origin[0];
this.originY_ = origin[1];
this.rotation_ = rotation;
this.rotateWithView_ = rotateWithView;
this.scale_ = scale;
this.width_ = size[0];
};
/**
* @inheritDoc
*/
ol.render.webgl.ImageReplay.prototype.setTextStyle = goog.abstractMethod;
/**
* @constructor
* @implements {ol.render.IReplayGroup}
* @param {number} tolerance Tolerance.
* @param {ol.Extent} maxExtent Max extent.
* @struct
*/
ol.render.webgl.ReplayGroup = function(tolerance, maxExtent) {
/**
* @type {ol.Extent}
* @private
*/
this.maxExtent_ = maxExtent;
/**
* @type {number}
* @private
*/
this.tolerance_ = tolerance;
/**
* ImageReplay only is supported at this point.
* @type {Object.<ol.render.ReplayType, ol.render.webgl.ImageReplay>}
* @private
*/
this.replays_ = {};
};
/**
* @param {ol.webgl.Context} context WebGL context.
* @return {function()} Delete resources function.
*/
ol.render.webgl.ReplayGroup.prototype.getDeleteResourcesFunction =
function(context) {
var functions = [];
var replayKey;
for (replayKey in this.replays_) {
functions.push(
this.replays_[replayKey].getDeleteResourcesFunction(context));
}
return goog.functions.sequence.apply(null, functions);
};
/**
* @param {ol.webgl.Context} context Context.
*/
ol.render.webgl.ReplayGroup.prototype.finish = function(context) {
var replayKey;
for (replayKey in this.replays_) {
this.replays_[replayKey].finish(context);
}
};
/**
* @inheritDoc
*/
ol.render.webgl.ReplayGroup.prototype.getReplay =
function(zIndex, replayType) {
var replay = this.replays_[replayType];
if (!goog.isDef(replay)) {
var constructor = ol.render.webgl.BATCH_CONSTRUCTORS_[replayType];
goog.asserts.assert(goog.isDef(constructor));
replay = new constructor(this.tolerance_, this.maxExtent_);
this.replays_[replayType] = replay;
}
return replay;
};
/**
* @inheritDoc
*/
ol.render.webgl.ReplayGroup.prototype.isEmpty = function() {
return goog.object.isEmpty(this.replays_);
};
/**
* @param {ol.webgl.Context} context Context.
* @param {ol.Coordinate} center Center.
* @param {number} resolution Resolution.
* @param {number} rotation Rotation.
* @param {ol.Size} size Size.
* @param {ol.Extent} extent Extent.
* @param {number} pixelRatio Pixel ratio.
* @param {number} opacity Global opacity.
* @param {number} brightness Global brightness.
* @param {number} contrast Global contrast.
* @param {number} hue Global hue.
* @param {number} saturation Global saturation.
* @param {Object} skippedFeaturesHash Ids of features to skip.
* @return {T|undefined} Callback result.
* @template T
*/
ol.render.webgl.ReplayGroup.prototype.replay = function(context,
center, resolution, rotation, size, extent, pixelRatio,
opacity, brightness, contrast, hue, saturation, skippedFeaturesHash) {
var i, ii, replay, result;
for (i = 0, ii = ol.render.REPLAY_ORDER.length; i < ii; ++i) {
replay = this.replays_[ol.render.REPLAY_ORDER[i]];
if (goog.isDef(replay) &&
ol.extent.intersects(extent, replay.getExtent())) {
result = replay.replay(context,
center, resolution, rotation, size, extent, pixelRatio,
opacity, brightness, contrast, hue, saturation, skippedFeaturesHash);
if (result) {
return result;
}
}
}
return undefined;
};
/**
* @const
* @private
* @type {Object.<ol.render.ReplayType,
* function(new: ol.render.webgl.ImageReplay, number,
* ol.Extent)>}
*/
ol.render.webgl.BATCH_CONSTRUCTORS_ = {
'Image': ol.render.webgl.ImageReplay
};

View File

@@ -207,6 +207,14 @@ ol.renderer.canvas.Layer.prototype.getTransform = function(frameState) {
};
/**
* @param {olx.FrameState} frameState Frame state.
* @param {ol.layer.LayerState} layerState Layer state.
* @return {boolean} whether composeFrame should be called.
*/
ol.renderer.canvas.Layer.prototype.prepareFrame = goog.abstractMethod;
/**
* @param {ol.Size} size Size.
* @return {boolean} True when the canvas with the current size does not exceed

View File

@@ -46,3 +46,11 @@ ol.renderer.dom.Layer.prototype.composeFrame = goog.nullFunction;
ol.renderer.dom.Layer.prototype.getTarget = function() {
return this.target;
};
/**
* @param {olx.FrameState} frameState Frame state.
* @param {ol.layer.LayerState} layerState Layer state.
* @return {boolean} whether composeFrame should be called.
*/
ol.renderer.dom.Layer.prototype.prepareFrame = goog.abstractMethod;

View File

@@ -6,7 +6,6 @@ goog.require('ol.ImageState');
goog.require('ol.TileRange');
goog.require('ol.TileState');
goog.require('ol.layer.Layer');
goog.require('ol.layer.LayerState');
goog.require('ol.source.Source');
goog.require('ol.source.State');
goog.require('ol.source.Tile');
@@ -95,14 +94,6 @@ ol.renderer.Layer.prototype.handleImageChange = function(event) {
};
/**
* @param {olx.FrameState} frameState Frame state.
* @param {ol.layer.LayerState} layerState Layer state.
* @return {boolean} whether composeFrame should be called.
*/
ol.renderer.Layer.prototype.prepareFrame = goog.abstractMethod;
/**
* @protected
*/

View File

@@ -101,7 +101,7 @@ ol.renderer.webgl.ImageLayer.prototype.forEachFeatureAtPixel =
* @inheritDoc
*/
ol.renderer.webgl.ImageLayer.prototype.prepareFrame =
function(frameState, layerState) {
function(frameState, layerState, context) {
var gl = this.getWebGLMapRenderer().getGL();

View File

@@ -10,7 +10,7 @@ goog.require('ol.render.webgl.Immediate');
goog.require('ol.renderer.Layer');
goog.require('ol.renderer.webgl.map.shader.Color');
goog.require('ol.renderer.webgl.map.shader.Default');
goog.require('ol.structs.Buffer');
goog.require('ol.webgl.Buffer');
@@ -26,9 +26,9 @@ ol.renderer.webgl.Layer = function(mapRenderer, layer) {
/**
* @private
* @type {ol.structs.Buffer}
* @type {ol.webgl.Buffer}
*/
this.arrayBuffer_ = new ol.structs.Buffer([
this.arrayBuffer_ = new ol.webgl.Buffer([
-1, -1, 0, 0,
1, -1, 1, 0,
-1, 1, 0, 1,
@@ -237,7 +237,16 @@ ol.renderer.webgl.Layer.prototype.dispatchComposeEvent_ =
function(type, context, frameState) {
var layer = this.getLayer();
if (layer.hasListener(type)) {
var render = new ol.render.webgl.Immediate(context, frameState.pixelRatio);
var viewState = frameState.viewState;
var resolution = viewState.resolution;
var pixelRatio = frameState.pixelRatio;
var extent = frameState.extent;
var center = viewState.center;
var rotation = viewState.rotation;
var size = frameState.size;
var render = new ol.render.webgl.Immediate(
context, center, resolution, rotation, size, extent, pixelRatio);
var composeEvent = new ol.render.Event(
type, layer, render, null, frameState, null, context);
layer.dispatchEvent(composeEvent);
@@ -286,3 +295,12 @@ ol.renderer.webgl.Layer.prototype.handleWebGLContextLost = function() {
this.framebuffer = null;
this.framebufferDimension = undefined;
};
/**
* @param {olx.FrameState} frameState Frame state.
* @param {ol.layer.LayerState} layerState Layer state.
* @param {ol.webgl.Context} context Context.
* @return {boolean} whether composeFrame should be called.
*/
ol.renderer.webgl.Layer.prototype.prepareFrame = goog.abstractMethod;

View File

@@ -20,13 +20,17 @@ goog.require('ol.dom');
goog.require('ol.layer.Image');
goog.require('ol.layer.Layer');
goog.require('ol.layer.Tile');
goog.require('ol.layer.Vector');
goog.require('ol.render.Event');
goog.require('ol.render.EventType');
goog.require('ol.render.webgl.Immediate');
goog.require('ol.render.webgl.ReplayGroup');
goog.require('ol.renderer.Map');
goog.require('ol.renderer.vector');
goog.require('ol.renderer.webgl.ImageLayer');
goog.require('ol.renderer.webgl.Layer');
goog.require('ol.renderer.webgl.TileLayer');
goog.require('ol.renderer.webgl.VectorLayer');
goog.require('ol.source.State');
goog.require('ol.structs.LRUCache');
goog.require('ol.structs.PriorityQueue');
@@ -250,6 +254,8 @@ ol.renderer.webgl.Map.prototype.createLayerRenderer = function(layer) {
return new ol.renderer.webgl.ImageLayer(this, layer);
} else if (ol.ENABLE_TILE && layer instanceof ol.layer.Tile) {
return new ol.renderer.webgl.TileLayer(this, layer);
} else if (ol.ENABLE_VECTOR && layer instanceof ol.layer.Vector) {
return new ol.renderer.webgl.VectorLayer(this, layer);
} else {
goog.asserts.fail();
return null;
@@ -266,11 +272,40 @@ ol.renderer.webgl.Map.prototype.dispatchComposeEvent_ =
function(type, frameState) {
var map = this.getMap();
if (map.hasListener(type)) {
var context = this.getContext();
var render = new ol.render.webgl.Immediate(context, frameState.pixelRatio);
var composeEvent = new ol.render.Event(
type, map, render, null, frameState, null, context);
var context = this.context_;
var extent = frameState.extent;
var size = frameState.size;
var viewState = frameState.viewState;
var pixelRatio = frameState.pixelRatio;
var resolution = viewState.resolution;
var center = viewState.center;
var rotation = viewState.rotation;
var tolerance = ol.renderer.vector.getTolerance(resolution, pixelRatio);
var vectorContext = new ol.render.webgl.Immediate(context,
center, resolution, rotation, size, extent, pixelRatio);
var replayGroup = new ol.render.webgl.ReplayGroup(tolerance, extent);
var composeEvent = new ol.render.Event(type, map, vectorContext,
replayGroup, frameState, null, context);
map.dispatchEvent(composeEvent);
replayGroup.finish(context);
if (!replayGroup.isEmpty()) {
// use default color values
var opacity = 1;
var brightness = 0;
var contrast = 1;
var hue = 0;
var saturation = 1;
replayGroup.replay(context, center, resolution, rotation, size, extent,
pixelRatio, opacity, brightness, contrast, hue, saturation, {});
}
replayGroup.getDeleteResourcesFunction(context)();
vectorContext.flush();
this.replayGroup = replayGroup;
}
};
@@ -455,7 +490,7 @@ ol.renderer.webgl.Map.prototype.renderFrame = function(frameState) {
layerState.sourceState == ol.source.State.READY) {
layerRenderer = this.getLayerRenderer(layerState.layer);
goog.asserts.assertInstanceof(layerRenderer, ol.renderer.webgl.Layer);
if (layerRenderer.prepareFrame(frameState, layerState)) {
if (layerRenderer.prepareFrame(frameState, layerState, context)) {
layerStatesToDraw.push(layerState);
}
}

View File

@@ -16,8 +16,8 @@ goog.require('ol.layer.Tile');
goog.require('ol.math');
goog.require('ol.renderer.webgl.Layer');
goog.require('ol.renderer.webgl.tilelayer.shader');
goog.require('ol.structs.Buffer');
goog.require('ol.tilecoord');
goog.require('ol.webgl.Buffer');
@@ -52,9 +52,9 @@ ol.renderer.webgl.TileLayer = function(mapRenderer, tileLayer) {
/**
* @private
* @type {ol.structs.Buffer}
* @type {ol.webgl.Buffer}
*/
this.renderArrayBuffer_ = new ol.structs.Buffer([
this.renderArrayBuffer_ = new ol.webgl.Buffer([
0, 0, 0, 1,
1, 0, 1, 1,
0, 1, 0, 0,
@@ -107,11 +107,10 @@ ol.renderer.webgl.TileLayer.prototype.handleWebGLContextLost = function() {
* @inheritDoc
*/
ol.renderer.webgl.TileLayer.prototype.prepareFrame =
function(frameState, layerState) {
function(frameState, layerState, context) {
var mapRenderer = this.getWebGLMapRenderer();
var context = mapRenderer.getContext();
var gl = mapRenderer.getGL();
var gl = context.getGL();
var viewState = frameState.viewState;
var projection = viewState.projection;

View File

@@ -0,0 +1,240 @@
goog.provide('ol.renderer.webgl.VectorLayer');
goog.require('goog.array');
goog.require('goog.asserts');
goog.require('goog.events');
goog.require('ol.ViewHint');
goog.require('ol.extent');
goog.require('ol.layer.Vector');
goog.require('ol.render.webgl.ReplayGroup');
goog.require('ol.renderer.vector');
goog.require('ol.renderer.webgl.Layer');
/**
* @constructor
* @extends {ol.renderer.webgl.Layer}
* @param {ol.renderer.Map} mapRenderer Map renderer.
* @param {ol.layer.Vector} vectorLayer Vector layer.
*/
ol.renderer.webgl.VectorLayer = function(mapRenderer, vectorLayer) {
goog.base(this, mapRenderer, vectorLayer);
/**
* @private
* @type {boolean}
*/
this.dirty_ = false;
/**
* @private
* @type {number}
*/
this.renderedRevision_ = -1;
/**
* @private
* @type {number}
*/
this.renderedResolution_ = NaN;
/**
* @private
* @type {ol.Extent}
*/
this.renderedExtent_ = ol.extent.createEmpty();
/**
* @private
* @type {function(ol.Feature, ol.Feature): number|null}
*/
this.renderedRenderOrder_ = null;
/**
* @private
* @type {ol.render.webgl.ReplayGroup}
*/
this.replayGroup_ = null;
};
goog.inherits(ol.renderer.webgl.VectorLayer, ol.renderer.webgl.Layer);
/**
* @inheritDoc
*/
ol.renderer.webgl.VectorLayer.prototype.composeFrame =
function(frameState, layerState, context) {
var viewState = frameState.viewState;
var replayGroup = this.replayGroup_;
if (!goog.isNull(replayGroup) && !replayGroup.isEmpty()) {
replayGroup.replay(context,
viewState.center, viewState.resolution, viewState.rotation,
frameState.size, frameState.extent, frameState.pixelRatio,
layerState.opacity, layerState.brightness, layerState.contrast,
layerState.hue, layerState.saturation, frameState.skippedFeatureUids);
}
};
/**
* @inheritDoc
*/
ol.renderer.webgl.VectorLayer.prototype.disposeInternal = function() {
var replayGroup = this.replayGroup_;
if (!goog.isNull(replayGroup)) {
var mapRenderer = this.getWebGLMapRenderer();
var context = mapRenderer.getContext();
replayGroup.getDeleteResourcesFunction(context)();
this.replayGroup_ = null;
}
goog.base(this, 'disposeInternal');
};
/**
* @inheritDoc
*/
ol.renderer.webgl.VectorLayer.prototype.forEachFeatureAtPixel =
function(coordinate, frameState, callback, thisArg) {
};
/**
* Handle changes in image style state.
* @param {goog.events.Event} event Image style change event.
* @private
*/
ol.renderer.webgl.VectorLayer.prototype.handleImageChange_ =
function(event) {
this.renderIfReadyAndVisible();
};
/**
* @inheritDoc
*/
ol.renderer.webgl.VectorLayer.prototype.prepareFrame =
function(frameState, layerState, context) {
var vectorLayer = /** @type {ol.layer.Vector} */ (this.getLayer());
goog.asserts.assertInstanceof(vectorLayer, ol.layer.Vector);
var vectorSource = vectorLayer.getSource();
this.updateAttributions(
frameState.attributions, vectorSource.getAttributions());
this.updateLogos(frameState, vectorSource);
if (!this.dirty_ && (frameState.viewHints[ol.ViewHint.ANIMATING] ||
frameState.viewHints[ol.ViewHint.INTERACTING])) {
return true;
}
var frameStateExtent = frameState.extent;
var viewState = frameState.viewState;
var projection = viewState.projection;
var resolution = viewState.resolution;
var pixelRatio = frameState.pixelRatio;
var vectorLayerRevision = vectorLayer.getRevision();
var vectorLayerRenderOrder = vectorLayer.getRenderOrder();
if (!goog.isDef(vectorLayerRenderOrder)) {
vectorLayerRenderOrder = ol.renderer.vector.defaultOrder;
}
if (!this.dirty_ &&
this.renderedResolution_ == resolution &&
this.renderedRevision_ == vectorLayerRevision &&
this.renderedRenderOrder_ == vectorLayerRenderOrder &&
ol.extent.containsExtent(this.renderedExtent_, frameStateExtent)) {
return true;
}
var extent = this.renderedExtent_;
var xBuffer = ol.extent.getWidth(frameStateExtent) / 4;
var yBuffer = ol.extent.getHeight(frameStateExtent) / 4;
extent[0] = frameStateExtent[0] - xBuffer;
extent[1] = frameStateExtent[1] - yBuffer;
extent[2] = frameStateExtent[2] + xBuffer;
extent[3] = frameStateExtent[3] + yBuffer;
if (!goog.isNull(this.replayGroup_)) {
frameState.postRenderFunctions.push(
this.replayGroup_.getDeleteResourcesFunction(context));
}
this.dirty_ = false;
var replayGroup = new ol.render.webgl.ReplayGroup(
ol.renderer.vector.getTolerance(resolution, pixelRatio),
extent);
vectorSource.loadFeatures(extent, resolution, projection);
var renderFeature =
/**
* @param {ol.Feature} feature Feature.
* @this {ol.renderer.webgl.VectorLayer}
*/
function(feature) {
var styles;
if (goog.isDef(feature.getStyleFunction())) {
styles = feature.getStyleFunction().call(feature, resolution);
} else if (goog.isDef(vectorLayer.getStyleFunction())) {
styles = vectorLayer.getStyleFunction()(feature, resolution);
}
if (goog.isDefAndNotNull(styles)) {
var dirty = this.renderFeature(
feature, resolution, pixelRatio, styles, replayGroup);
this.dirty_ = this.dirty_ || dirty;
}
};
if (!goog.isNull(vectorLayerRenderOrder)) {
/** @type {Array.<ol.Feature>} */
var features = [];
vectorSource.forEachFeatureInExtentAtResolution(extent, resolution,
/**
* @param {ol.Feature} feature Feature.
*/
function(feature) {
features.push(feature);
}, this);
goog.array.sort(features, vectorLayerRenderOrder);
goog.array.forEach(features, renderFeature, this);
} else {
vectorSource.forEachFeatureInExtentAtResolution(
extent, resolution, renderFeature, this);
}
replayGroup.finish(context);
this.renderedResolution_ = resolution;
this.renderedRevision_ = vectorLayerRevision;
this.renderedRenderOrder_ = vectorLayerRenderOrder;
this.replayGroup_ = replayGroup;
return true;
};
/**
* @param {ol.Feature} feature Feature.
* @param {number} resolution Resolution.
* @param {number} pixelRatio Pixel ratio.
* @param {Array.<ol.style.Style>} styles Array of styles
* @param {ol.render.webgl.ReplayGroup} replayGroup Replay group.
* @return {boolean} `true` if an image is loading.
*/
ol.renderer.webgl.VectorLayer.prototype.renderFeature =
function(feature, resolution, pixelRatio, styles, replayGroup) {
if (!goog.isDefAndNotNull(styles)) {
return false;
}
var i, ii, loading = false;
for (i = 0, ii = styles.length; i < ii; ++i) {
loading = ol.renderer.vector.renderFeature(
replayGroup, feature, styles[i],
ol.renderer.vector.getSquaredTolerance(resolution, pixelRatio),
feature, this.handleImageChange_, this) || loading;
}
return loading;
};

View File

@@ -1,257 +0,0 @@
goog.provide('ol.structs.Buffer');
goog.require('goog.array');
goog.require('goog.asserts');
goog.require('goog.webgl');
goog.require('ol');
goog.require('ol.structs.IntegerSet');
/**
* @enum {number}
*/
ol.structs.BufferUsage = {
STATIC_DRAW: goog.webgl.STATIC_DRAW,
STREAM_DRAW: goog.webgl.STREAM_DRAW,
DYNAMIC_DRAW: goog.webgl.DYNAMIC_DRAW
};
/**
* @constructor
* @param {Array.<number>=} opt_arr Array.
* @param {number=} opt_used Used.
* @param {number=} opt_usage Usage.
* @struct
*/
ol.structs.Buffer = function(opt_arr, opt_used, opt_usage) {
/**
* @private
* @type {Array.<number>}
*/
this.arr_ = goog.isDef(opt_arr) ? opt_arr : [];
/**
* @private
* @type {Array.<ol.structs.IntegerSet>}
*/
this.dirtySets_ = [];
/**
* @private
* @type {ol.structs.IntegerSet}
*/
this.freeSet_ = new ol.structs.IntegerSet();
var used = goog.isDef(opt_used) ? opt_used : this.arr_.length;
if (used < this.arr_.length) {
this.freeSet_.addRange(used, this.arr_.length);
}
if (ol.BUFFER_REPLACE_UNUSED_ENTRIES_WITH_NANS) {
var arr = this.arr_;
var n = arr.length;
var i;
for (i = used; i < n; ++i) {
arr[i] = NaN;
}
}
/**
* @private
* @type {?Float32Array}
*/
this.split32_ = null;
/**
* @private
* @type {ol.structs.IntegerSet}
*/
this.split32DirtySet_ = null;
/**
* @private
* @type {number}
*/
this.usage_ = goog.isDef(opt_usage) ?
opt_usage : ol.structs.BufferUsage.STATIC_DRAW;
};
/**
* @param {number} size Size.
* @return {number} Offset.
*/
ol.structs.Buffer.prototype.allocate = function(size) {
goog.asserts.assert(size > 0);
var offset = this.freeSet_.findRange(size);
goog.asserts.assert(offset != -1); // FIXME
this.freeSet_.removeRange(offset, offset + size);
return offset;
};
/**
* @param {Array.<number>} values Values.
* @return {number} Offset.
*/
ol.structs.Buffer.prototype.add = function(values) {
var size = values.length;
var offset = this.allocate(size);
var i;
for (i = 0; i < size; ++i) {
this.arr_[offset + i] = values[i];
}
this.markDirty(size, offset);
return offset;
};
/**
* @param {ol.structs.IntegerSet} dirtySet Dirty set.
*/
ol.structs.Buffer.prototype.addDirtySet = function(dirtySet) {
goog.asserts.assert(!goog.array.contains(this.dirtySets_, dirtySet));
this.dirtySets_.push(dirtySet);
};
/**
* @param {function(this: T, number, number)} f Callback.
* @param {T=} opt_this The object to use as `this` in `f`.
* @template T
*/
ol.structs.Buffer.prototype.forEachRange = function(f, opt_this) {
if (this.arr_.length !== 0) {
this.freeSet_.forEachRangeInverted(0, this.arr_.length, f, opt_this);
}
};
/**
* @return {Array.<number>} Array.
*/
ol.structs.Buffer.prototype.getArray = function() {
return this.arr_;
};
/**
* @return {number} Count.
*/
ol.structs.Buffer.prototype.getCount = function() {
return this.arr_.length - this.freeSet_.getSize();
};
/**
* @return {ol.structs.IntegerSet} Free set.
*/
ol.structs.Buffer.prototype.getFreeSet = function() {
return this.freeSet_;
};
/**
* Returns a Float32Array twice the length of the buffer containing each value
* split into two 32-bit floating point values that, when added together,
* approximate the original value. Even indicies contain the high bits, odd
* indicies contain the low bits.
* @see http://blogs.agi.com/insight3d/index.php/2008/09/03/precisions-precisions/
* @return {Float32Array} Split.
*/
ol.structs.Buffer.prototype.getSplit32 = function() {
var arr = this.arr_;
var n = arr.length;
if (goog.isNull(this.split32DirtySet_)) {
this.split32DirtySet_ = new ol.structs.IntegerSet([0, n]);
this.addDirtySet(this.split32DirtySet_);
}
if (goog.isNull(this.split32_)) {
this.split32_ = new Float32Array(2 * n);
}
var split32 = this.split32_;
this.split32DirtySet_.forEachRange(function(start, stop) {
var doubleHigh, i, j, value;
for (i = start, j = 2 * start; i < stop; ++i, j += 2) {
value = arr[i];
if (value < 0) {
doubleHigh = 65536 * Math.floor(-value / 65536);
split32[j] = -doubleHigh;
split32[j + 1] = value + doubleHigh;
} else {
doubleHigh = 65536 * Math.floor(value / 65536);
split32[j] = doubleHigh;
split32[j + 1] = value - doubleHigh;
}
}
});
this.split32DirtySet_.clear();
return this.split32_;
};
/**
* @return {number} Usage.
*/
ol.structs.Buffer.prototype.getUsage = function() {
return this.usage_;
};
/**
* @param {number} size Size.
* @param {number} offset Offset.
*/
ol.structs.Buffer.prototype.markDirty = function(size, offset) {
var i, ii;
for (i = 0, ii = this.dirtySets_.length; i < ii; ++i) {
this.dirtySets_[i].addRange(offset, offset + size);
}
};
/**
* @param {number} size Size.
* @param {number} offset Offset.
*/
ol.structs.Buffer.prototype.remove = function(size, offset) {
var i, ii;
this.freeSet_.addRange(offset, offset + size);
for (i = 0, ii = this.dirtySets_.length; i < ii; ++i) {
this.dirtySets_[i].removeRange(offset, offset + size);
}
if (ol.BUFFER_REPLACE_UNUSED_ENTRIES_WITH_NANS) {
var arr = this.arr_;
for (i = 0; i < size; ++i) {
arr[offset + i] = NaN;
}
}
};
/**
* @param {ol.structs.IntegerSet} dirtySet Dirty set.
*/
ol.structs.Buffer.prototype.removeDirtySet = function(dirtySet) {
var removed = goog.array.remove(this.dirtySets_, dirtySet);
goog.asserts.assert(removed);
};
/**
* @param {Array.<number>} values Values.
* @param {number} offset Offset.
*/
ol.structs.Buffer.prototype.set = function(values, offset) {
var arr = this.arr_;
var n = values.length;
goog.asserts.assert(0 <= offset && offset + n <= arr.length);
var i;
for (i = 0; i < n; ++i) {
arr[offset + i] = values[i];
}
this.markDirty(n, offset);
};

View File

@@ -0,0 +1,16 @@
goog.provide('ol.structs.IHasChecksum');
/**
* @interface
*/
ol.structs.IHasChecksum = function() {
};
/**
* @return {string} The checksum.
*/
ol.structs.IHasChecksum.prototype.getChecksum = function() {
};

View File

@@ -1,330 +0,0 @@
goog.provide('ol.structs.IntegerSet');
goog.require('goog.asserts');
/**
* A set of integers represented as a set of integer ranges.
* This implementation is designed for the case when the number of distinct
* integer ranges is small.
* @constructor
* @struct
* @param {Array.<number>=} opt_arr Array.
*/
ol.structs.IntegerSet = function(opt_arr) {
/**
* @private
* @type {Array.<number>}
*/
this.arr_ = goog.isDef(opt_arr) ? opt_arr : [];
if (goog.DEBUG) {
this.assertValid();
}
};
/**
* @param {number} addStart Start.
* @param {number} addStop Stop.
*/
ol.structs.IntegerSet.prototype.addRange = function(addStart, addStop) {
goog.asserts.assert(addStart <= addStop);
if (addStart == addStop) {
return;
}
var arr = this.arr_;
var n = arr.length;
var i;
for (i = 0; i < n; i += 2) {
if (addStart <= arr[i]) {
// FIXME check if splice is really needed
arr.splice(i, 0, addStart, addStop);
this.compactRanges_();
return;
}
}
arr.push(addStart, addStop);
this.compactRanges_();
};
/**
* FIXME empty description for jsdoc
*/
ol.structs.IntegerSet.prototype.assertValid = function() {
var arr = this.arr_;
var n = arr.length;
goog.asserts.assert(n % 2 === 0);
var i;
for (i = 1; i < n; ++i) {
goog.asserts.assert(arr[i] > arr[i - 1]);
}
};
/**
* FIXME empty description for jsdoc
*/
ol.structs.IntegerSet.prototype.clear = function() {
this.arr_.length = 0;
};
/**
* @private
*/
ol.structs.IntegerSet.prototype.compactRanges_ = function() {
var arr = this.arr_;
var n = arr.length;
var rangeIndex = 0;
var i;
for (i = 0; i < n; i += 2) {
if (arr[i] == arr[i + 1]) {
// pass
} else if (rangeIndex > 0 &&
arr[rangeIndex - 2] <= arr[i] &&
arr[i] <= arr[rangeIndex - 1]) {
arr[rangeIndex - 1] = Math.max(arr[rangeIndex - 1], arr[i + 1]);
} else {
arr[rangeIndex++] = arr[i];
arr[rangeIndex++] = arr[i + 1];
}
}
arr.length = rangeIndex;
};
/**
* Finds the start of smallest range that is at least of length minSize, or -1
* if no such range exists.
* @param {number} minSize Minimum size.
* @return {number} Index.
*/
ol.structs.IntegerSet.prototype.findRange = function(minSize) {
goog.asserts.assert(minSize > 0);
var arr = this.arr_;
var n = arr.length;
var bestIndex = -1;
var bestSize, i, size;
for (i = 0; i < n; i += 2) {
size = arr[i + 1] - arr[i];
if (size == minSize) {
return arr[i];
} else if (size > minSize && (bestIndex == -1 || size < bestSize)) {
bestIndex = arr[i];
bestSize = size;
}
}
return bestIndex;
};
/**
* Calls f with each integer range.
* @param {function(this: T, number, number)} f Callback.
* @param {T=} opt_this The object to use as `this` in `f`.
* @template T
*/
ol.structs.IntegerSet.prototype.forEachRange = function(f, opt_this) {
var arr = this.arr_;
var n = arr.length;
var i;
for (i = 0; i < n; i += 2) {
f.call(opt_this, arr[i], arr[i + 1]);
}
};
/**
* Calls f with each integer range not in [start, stop) - 'this'.
* @param {number} start Start.
* @param {number} stop Stop.
* @param {function(this: T, number, number)} f Callback.
* @param {T=} opt_this The object to use as `this` in `f`.
* @template T
*/
ol.structs.IntegerSet.prototype.forEachRangeInverted =
function(start, stop, f, opt_this) {
goog.asserts.assert(start < stop);
var arr = this.arr_;
var n = arr.length;
if (n === 0) {
f.call(opt_this, start, stop);
} else {
if (start < arr[0]) {
f.call(opt_this, start, arr[0]);
}
var i;
for (i = 1; i < n - 1; i += 2) {
f.call(opt_this, arr[i], arr[i + 1]);
}
if (arr[n - 1] < stop) {
f.call(opt_this, arr[n - 1], stop);
}
}
};
/**
* @return {Array.<number>} Array.
*/
ol.structs.IntegerSet.prototype.getArray = function() {
return this.arr_;
};
/**
* Returns the first element in the set, or -1 if the set is empty.
* @return {number} Start.
*/
ol.structs.IntegerSet.prototype.getFirst = function() {
return this.arr_.length === 0 ? -1 : this.arr_[0];
};
/**
* Returns the first integer after the last element in the set, or -1 if the
* set is empty.
* @return {number} Last.
*/
ol.structs.IntegerSet.prototype.getLast = function() {
var n = this.arr_.length;
return n === 0 ? -1 : this.arr_[n - 1];
};
/**
* Returns the number of integers in the set.
* @return {number} Size.
*/
ol.structs.IntegerSet.prototype.getSize = function() {
var arr = this.arr_;
var n = arr.length;
var size = 0;
var i;
for (i = 0; i < n; i += 2) {
size += arr[i + 1] - arr[i];
}
return size;
};
/**
* @param {number} start Start.
* @param {number} stop Stop.
* @return {boolean} Intersects range.
*/
ol.structs.IntegerSet.prototype.intersectsRange = function(start, stop) {
goog.asserts.assert(start <= stop);
if (start == stop) {
return false;
} else {
var arr = this.arr_;
var n = arr.length;
var i = 0;
for (i = 0; i < n; i += 2) {
if (arr[i] <= start && start < arr[i + 1] ||
arr[i] < stop && stop - 1 < arr[i + 1] ||
start < arr[i] && arr[i + 1] <= stop) {
return true;
}
}
return false;
}
};
/**
* @return {boolean} Is empty.
*/
ol.structs.IntegerSet.prototype.isEmpty = function() {
return this.arr_.length === 0;
};
/**
* @return {Array.<number>} Array.
*/
ol.structs.IntegerSet.prototype.pack = function() {
return this.arr_;
};
/**
* @param {number} removeStart Start.
* @param {number} removeStop Stop.
*/
ol.structs.IntegerSet.prototype.removeRange =
function(removeStart, removeStop) {
// FIXME this could be more efficient
goog.asserts.assert(removeStart <= removeStop);
var arr = this.arr_;
var n = arr.length;
var i;
for (i = 0; i < n; i += 2) {
if (removeStop < arr[i] || arr[i + 1] < removeStart) {
continue;
} else if (arr[i] > removeStop) {
break;
}
if (removeStart < arr[i]) {
if (removeStop == arr[i]) {
break;
} else if (removeStop < arr[i + 1]) {
arr[i] = Math.max(arr[i], removeStop);
break;
} else {
arr.splice(i, 2);
i -= 2;
n -= 2;
}
} else if (removeStart == arr[i]) {
if (removeStop < arr[i + 1]) {
arr[i] = removeStop;
break;
} else if (removeStop == arr[i + 1]) {
arr.splice(i, 2);
break;
} else {
arr.splice(i, 2);
i -= 2;
n -= 2;
}
} else {
if (removeStop < arr[i + 1]) {
arr.splice(i, 2, arr[i], removeStart, removeStop, arr[i + 1]);
break;
} else if (removeStop == arr[i + 1]) {
arr[i + 1] = removeStart;
break;
} else {
arr[i + 1] = removeStart;
}
}
}
this.compactRanges_();
};
if (goog.DEBUG) {
/**
* @return {string} String.
*/
ol.structs.IntegerSet.prototype.toString = function() {
var arr = this.arr_;
var n = arr.length;
var result = new Array(n / 2);
var resultIndex = 0;
var i;
for (i = 0; i < n; i += 2) {
result[resultIndex++] = arr[i] + '-' + arr[i + 1];
}
return result.join(', ');
};
}

View File

@@ -0,0 +1,440 @@
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');
goog.require('ol');
/**
* Provides information for an image inside an atlas manager.
* `offsetX` and `offsetY` is the position of the image inside
* the atlas image `image`.
* `hitOffsetX` and `hitOffsetY` ist the position of the hit-detection image
* inside the hit-detection atlas image `hitImage` (only when a hit-detection
* image was created for this image).
* @typedef {{offsetX: number, offsetY: number, image: HTMLCanvasElement,
* hitOffsetX: number, hitOffsetY: number, hitImage: HTMLCanvasElement}}
*/
ol.style.AtlasManagerInfo;
/**
* 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).
*
* If an application uses many images or very large images, it is recommended
* 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.initialSize) ?
options.initialSize : ol.INITIAL_ATLAS_SIZE;
/**
* The maximum size in pixels of atlas images.
* @private
* @type {number}
*/
this.maxSize_ = goog.isDef(options.maxSize) ?
options.maxSize : ol.MAX_ATLAS_SIZE != -1 ?
ol.MAX_ATLAS_SIZE : goog.isDef(ol.WEBGL_MAX_TEXTURE_SIZE) ?
ol.WEBGL_MAX_TEXTURE_SIZE : 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_)];
/**
* The size in pixels of the latest atlas image for hit-detection images.
* @private
* @type {number}
*/
this.currentHitSize_ = this.currentSize_;
/**
* @private
* @type {Array.<ol.style.Atlas>}
*/
this.hitAtlases_ = [new ol.style.Atlas(this.currentHitSize_, this.space_)];
};
/**
* @param {string} id The identifier of the entry to check.
* @return {?ol.style.AtlasManagerInfo} 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) {
/** @type {?ol.style.AtlasInfo} */
var info = this.getInfo_(this.atlases_, id);
if (goog.isNull(info)) {
return null;
}
/** @type {?ol.style.AtlasInfo} */
var hitInfo = this.getInfo_(this.hitAtlases_, id);
return this.mergeInfos_(info, hitInfo);
};
/**
* @private
* @param {Array.<ol.style.Atlas>} atlases The atlases to search.
* @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 atlases.
*/
ol.style.AtlasManager.prototype.getInfo_ = function(atlases, id) {
var atlas, info, i, ii;
for (i = 0, ii = atlases.length; i < ii; ++i) {
atlas = atlases[i];
info = atlas.get(id);
if (!goog.isNull(info)) {
return info;
}
}
return null;
};
/**
* @private
* @param {ol.style.AtlasInfo} info The info for the real image.
* @param {?ol.style.AtlasInfo} hitInfo The info for the hit-detection
* image.
* @return {?ol.style.AtlasManagerInfo} The position and atlas image for the
* entry, or `null` if the entry is not part of the atlases.
*/
ol.style.AtlasManager.prototype.mergeInfos_ = function(info, hitInfo) {
return /** @type {ol.style.AtlasManagerInfo} */ ({
offsetX: info.offsetX,
offsetY: info.offsetY,
image: info.image,
hitOffsetX: goog.isNull(hitInfo) ? undefined : hitInfo.offsetX,
hitOffsetY: goog.isNull(hitInfo) ? undefined : hitInfo.offsetY,
hitImage: goog.isNull(hitInfo) ? undefined : hitInfo.image
});
};
/**
* 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).
*
* If `renderHitCallback` is provided, the image (or the hit-detection version
* of the image) will be rendered into a separate hit-detection atlas image.
*
* @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 {function(CanvasRenderingContext2D, number, number)=}
* opt_renderHitCallback Called to render a hit-detection image onto a hit
* detection atlas image.
* @param {Object=} opt_this Value to use as `this` when executing
* `renderCallback` and `renderHitCallback`.
* @return {?ol.style.AtlasManagerInfo} 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_renderHitCallback, opt_this) {
if (width + this.space_ > this.maxSize_ ||
height + this.space_ > this.maxSize_) {
return null;
}
/** @type {?ol.style.AtlasInfo} */
var info = this.add_(false,
id, width, height, renderCallback, opt_this);
if (goog.isNull(info)) {
return null;
}
/** @type {?ol.style.AtlasInfo} */
var hitInfo = null;
if (goog.isDef(opt_renderHitCallback)) {
hitInfo = this.add_(true,
id, width, height, opt_renderHitCallback, opt_this);
}
return this.mergeInfos_(info, hitInfo);
};
/**
* @private
* @param {boolean} isHitAtlas If the hit-detection atlases are used.
* @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` and `renderHitCallback`.
* @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(isHitAtlas, id, width, height,
renderCallback, opt_this) {
var atlases = (isHitAtlas) ? this.hitAtlases_ : this.atlases_;
var atlas, info, i, ii;
for (i = 0, ii = atlases.length; i < ii; ++i) {
atlas = atlases[i];
info = atlas.add(id, width, height, renderCallback, opt_this);
if (!goog.isNull(info)) {
return info;
} else if (goog.isNull(info) && 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.
var size;
if (isHitAtlas) {
size = Math.min(this.currentHitSize_ * 2, this.maxSize_);
this.currentHitSize_ = size;
} else {
size = Math.min(this.currentSize_ * 2, this.maxSize_);
this.currentSize_ = size;
}
atlas = new ol.style.Atlas(size, this.space_);
atlases.push(atlas);
// run the loop another time
++ii;
}
}
goog.asserts.fail();
};
/**
* 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;
/**
* 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.
* 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.
*/
ol.style.Atlas = function(size, space) {
/**
* @private
* @type {number}
*/
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;

View File

@@ -1,10 +1,12 @@
goog.provide('ol.style.Circle');
goog.require('goog.asserts');
goog.require('goog.dom');
goog.require('goog.dom.TagName');
goog.require('ol.color');
goog.require('ol.has');
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');
@@ -19,18 +21,24 @@ 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) {
var options = goog.isDef(opt_options) ? opt_options : {};
/**
* @private
* @type {Array.<string>}
*/
this.checksums_ = null;
/**
* @private
* @type {HTMLCanvasElement}
*/
this.canvas_ = /** @type {HTMLCanvasElement} */
(goog.dom.createElement(goog.dom.TagName.CANVAS));
this.canvas_ = null;
/**
* @private
@@ -46,9 +54,9 @@ ol.style.Circle = function(opt_options) {
/**
* @private
* @type {Array.<number>}
* @type {ol.style.Stroke}
*/
this.origin_ = [0, 0];
this.stroke_ = goog.isDef(options.stroke) ? options.stroke : null;
/**
* @private
@@ -58,23 +66,41 @@ ol.style.Circle = function(opt_options) {
/**
* @private
* @type {ol.style.Stroke}
* @type {Array.<number>}
*/
this.stroke_ = goog.isDef(options.stroke) ? options.stroke : null;
var size = this.render_();
this.origin_ = [0, 0];
/**
* @private
* @type {Array.<number>}
*/
this.anchor_ = [size / 2, size / 2];
this.hitDetectionOrigin_ = [0, 0];
/**
* @private
* @type {Array.<number>}
*/
this.anchor_ = null;
/**
* @private
* @type {ol.Size}
*/
this.size_ = [size, size];
this.size_ = null;
/**
* @private
* @type {ol.Size}
*/
this.imageSize_ = null;
/**
* @private
* @type {ol.Size}
*/
this.hitDetectionImageSize_ = null;
this.render_(options.atlasManager);
/**
* @type {boolean}
@@ -138,6 +164,22 @@ ol.style.Circle.prototype.getImageState = function() {
};
/**
* @inheritDoc
*/
ol.style.Circle.prototype.getImageSize = function() {
return this.imageSize_;
};
/**
* @inheritDoc
*/
ol.style.Circle.prototype.getHitDetectionImageSize = function() {
return this.hitDetectionImageSize_;
};
/**
* @inheritDoc
* @api
@@ -147,6 +189,14 @@ ol.style.Circle.prototype.getOrigin = function() {
};
/**
* @inheritDoc
*/
ol.style.Circle.prototype.getHitDetectionOrigin = function() {
return this.hitDetectionOrigin_;
};
/**
* @return {number} Radius.
* @api
@@ -193,79 +243,216 @@ ol.style.Circle.prototype.unlistenImageChange = goog.nullFunction;
/**
* @private
* @return {number} Size.
* @typedef {{strokeStyle: (string|undefined), strokeWidth: number,
* size: number, lineDash: Array.<number>}}
*/
ol.style.Circle.prototype.render_ = function() {
var canvas = this.canvas_;
var strokeStyle, strokeWidth, lineDash;
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 imageSize;
var lineDash = null;
var strokeStyle;
var strokeWidth = 0;
if (!goog.isNull(this.stroke_)) {
strokeStyle = ol.color.asString(this.stroke_.getColor());
strokeWidth = this.stroke_.getWidth();
if (!goog.isDef(strokeWidth)) {
strokeWidth = ol.render.canvas.defaultLineWidth;
}
lineDash = this.stroke_.getLineDash();
if (!ol.has.CANVAS_LINE_DASH) {
lineDash = null;
}
}
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,
lineDash: lineDash
};
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;
size = this.canvas_.width;
imageSize = size;
// draw the circle on the canvas
var context = /** @type {CanvasRenderingContext2D} */
(canvas.getContext('2d'));
context.arc(size / 2, size / 2, this.radius_, 0, 2 * Math.PI, true);
(this.canvas_.getContext('2d'));
this.draw_(renderOptions, context, 0, 0);
this.createHitDetectionCanvas_(renderOptions);
} else {
// an atlas manager is used, add the symbol to an atlas
size = Math.round(size);
var hasCustomHitDetectionImage = goog.isNull(this.fill_);
var renderHitDetectionCallback;
if (hasCustomHitDetectionImage) {
// render the hit-detection image into a separate atlas image
renderHitDetectionCallback =
goog.bind(this.drawHitDetectionCanvas_, this, renderOptions);
}
var id = this.getChecksum();
var info = atlasManager.add(
id, size, size, goog.bind(this.draw_, this, renderOptions),
renderHitDetectionCallback);
goog.asserts.assert(info !== null, 'circle radius is too large');
this.canvas_ = info.image;
this.origin_ = [info.offsetX, info.offsetY];
imageSize = info.image.width;
if (hasCustomHitDetectionImage) {
this.hitDetectionCanvas_ = info.hitImage;
this.hitDetectionOrigin_ = [info.hitOffsetX, info.hitOffsetY];
this.hitDetectionImageSize_ =
[info.hitImage.width, info.hitImage.height];
} else {
this.hitDetectionCanvas_ = this.canvas_;
this.hitDetectionOrigin_ = this.origin_;
this.hitDetectionImageSize_ = [imageSize, imageSize];
}
}
this.anchor_ = [size / 2, size / 2];
this.size_ = [size, size];
this.imageSize_ = [imageSize, imageSize];
};
/**
* @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;
lineDash = this.stroke_.getLineDash();
if (ol.has.CANVAS_LINE_DASH && !goog.isNull(lineDash)) {
context.setLineDash(lineDash);
context.strokeStyle = renderOptions.strokeStyle;
context.lineWidth = renderOptions.strokeWidth;
if (!goog.isNull(renderOptions.lineDash)) {
context.setLineDash(renderOptions.lineDash);
}
context.lineWidth = strokeWidth;
context.stroke();
}
context.closePath();
};
// deal with the hit detection canvas
/**
* @private
* @param {ol.style.Circle.RenderOptions} renderOptions
*/
ol.style.Circle.prototype.createHitDetectionCanvas_ = function(renderOptions) {
this.hitDetectionImageSize_ = [renderOptions.size, renderOptions.size];
if (!goog.isNull(this.fill_)) {
this.hitDetectionCanvas_ = canvas;
} else {
this.hitDetectionCanvas_ = this.canvas_;
return;
}
// if no fill style is set, create an extra hit-detection image with a
// default fill style
this.hitDetectionCanvas_ = /** @type {HTMLCanvasElement} */
(goog.dom.createElement(goog.dom.TagName.CANVAS));
canvas = this.hitDetectionCanvas_;
var canvas = this.hitDetectionCanvas_;
canvas.height = size;
canvas.width = size;
canvas.height = renderOptions.size;
canvas.width = renderOptions.size;
context = /** @type {CanvasRenderingContext2D} */
var context = /** @type {CanvasRenderingContext2D} */
(canvas.getContext('2d'));
context.arc(size / 2, size / 2, this.radius_, 0, 2 * Math.PI, true);
this.drawHitDetectionCanvas_(renderOptions, context, 0, 0);
};
/**
* @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.drawHitDetectionCanvas_ =
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);
context.fillStyle = ol.render.canvas.defaultFillStyle;
context.fill();
if (!goog.isNull(this.stroke_)) {
context.strokeStyle = strokeStyle;
lineDash = this.stroke_.getLineDash();
if (ol.has.CANVAS_LINE_DASH && !goog.isNull(lineDash)) {
context.setLineDash(lineDash);
context.strokeStyle = renderOptions.strokeStyle;
context.lineWidth = renderOptions.strokeWidth;
if (!goog.isNull(renderOptions.lineDash)) {
context.setLineDash(renderOptions.lineDash);
}
context.lineWidth = strokeWidth;
context.stroke();
}
context.closePath();
};
/**
* @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 size;
return this.checksums_[0];
};

View File

@@ -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 {string|undefined}
*/
this.checksum_ = undefined;
};
@@ -39,4 +49,18 @@ ol.style.Fill.prototype.getColor = function() {
*/
ol.style.Fill.prototype.setColor = function(color) {
this.color_ = color;
this.checksum_ = undefined;
};
/**
* @inheritDoc
*/
ol.style.Fill.prototype.getChecksum = function() {
if (!goog.isDef(this.checksum_)) {
this.checksum_ = 'f' + (!goog.isNull(this.color_) ?
ol.color.asString(this.color_) : '-');
}
return this.checksum_;
};

View File

@@ -246,6 +246,14 @@ ol.style.Icon.prototype.getImageSize = function() {
};
/**
* @inheritDoc
*/
ol.style.Icon.prototype.getHitDetectionImageSize = function() {
return this.getImageSize();
};
/**
* @inheritDoc
*/
@@ -293,6 +301,14 @@ ol.style.Icon.prototype.getOrigin = function() {
};
/**
* @inheritDoc
*/
ol.style.Icon.prototype.getHitDetectionOrigin = function() {
return this.getOrigin();
};
/**
* @return {string|undefined} Image src.
* @api

View File

@@ -126,12 +126,6 @@ ol.style.Image.prototype.getAnchor = goog.abstractMethod;
ol.style.Image.prototype.getImage = goog.abstractMethod;
/**
* @return {ol.style.ImageState} Image state.
*/
ol.style.Image.prototype.getImageState = goog.abstractMethod;
/**
* @param {number} pixelRatio Pixel ratio.
* @return {HTMLCanvasElement|HTMLVideoElement|Image} Image element.
@@ -139,6 +133,24 @@ ol.style.Image.prototype.getImageState = goog.abstractMethod;
ol.style.Image.prototype.getHitDetectionImage = goog.abstractMethod;
/**
* @return {ol.style.ImageState} Image state.
*/
ol.style.Image.prototype.getImageState = goog.abstractMethod;
/**
* @return {ol.Size} Image size.
*/
ol.style.Image.prototype.getImageSize = goog.abstractMethod;
/**
* @return {ol.Size} Size of the hit-detection image.
*/
ol.style.Image.prototype.getHitDetectionImageSize = goog.abstractMethod;
/**
* @function
* @return {Array.<number>} Origin.
@@ -146,6 +158,13 @@ ol.style.Image.prototype.getHitDetectionImage = goog.abstractMethod;
ol.style.Image.prototype.getOrigin = goog.abstractMethod;
/**
* @function
* @return {Array.<number>} Origin for the hit-detection image.
*/
ol.style.Image.prototype.getHitDetectionOrigin = goog.abstractMethod;
/**
* @function
* @return {ol.Size} Size.

View File

@@ -22,18 +22,24 @@ goog.require('ol.style.Stroke');
* @constructor
* @param {olx.style.RegularShapeOptions=} opt_options Options.
* @extends {ol.style.Image}
* @implements {ol.structs.IHasChecksum}
* @api
*/
ol.style.RegularShape = function(opt_options) {
var options = goog.isDef(opt_options) ? opt_options : {};
/**
* @private
* @type {Array.<string>}
*/
this.checksums_ = null;
/**
* @private
* @type {HTMLCanvasElement}
*/
this.canvas_ = /** @type {HTMLCanvasElement} */
(goog.dom.createElement(goog.dom.TagName.CANVAS));
this.canvas_ = null;
/**
* @private
@@ -53,6 +59,12 @@ ol.style.RegularShape = function(opt_options) {
*/
this.origin_ = [0, 0];
/**
* @private
* @type {Array.<number>}
*/
this.hitDetectionOrigin_ = [0, 0];
/**
* @private
* @type {number}
@@ -88,19 +100,31 @@ ol.style.RegularShape = function(opt_options) {
*/
this.stroke_ = goog.isDef(options.stroke) ? options.stroke : null;
var size = this.render_();
/**
* @private
* @type {Array.<number>}
*/
this.anchor_ = [size / 2, size / 2];
this.anchor_ = null;
/**
* @private
* @type {ol.Size}
*/
this.size_ = [size, size];
this.size_ = null;
/**
* @private
* @type {ol.Size}
*/
this.imageSize_ = null;
/**
* @private
* @type {ol.Size}
*/
this.hitDetectionImageSize_ = null;
this.render_(options.atlasManager);
/**
* @type {boolean}
@@ -155,6 +179,22 @@ ol.style.RegularShape.prototype.getImage = function(pixelRatio) {
};
/**
* @inheritDoc
*/
ol.style.RegularShape.prototype.getImageSize = function() {
return this.imageSize_;
};
/**
* @inheritDoc
*/
ol.style.RegularShape.prototype.getHitDetectionImageSize = function() {
return this.hitDetectionImageSize_;
};
/**
* @inheritDoc
*/
@@ -172,6 +212,14 @@ ol.style.RegularShape.prototype.getOrigin = function() {
};
/**
* @inheritDoc
*/
ol.style.RegularShape.prototype.getHitDetectionOrigin = function() {
return this.hitDetectionOrigin_;
};
/**
* @return {number} Radius.
* @api
@@ -227,36 +275,116 @@ ol.style.RegularShape.prototype.unlistenImageChange = goog.nullFunction;
/**
* @private
* @return {number} Size.
* @typedef {{strokeStyle: (string|undefined), strokeWidth: number,
* size: number, lineDash: Array.<number>}}
*/
ol.style.RegularShape.prototype.render_ = function() {
var canvas = this.canvas_;
var strokeStyle, strokeWidth, lineDash;
ol.style.RegularShape.RenderOptions;
if (goog.isNull(this.stroke_)) {
strokeWidth = 0;
} else {
/**
* @private
* @param {ol.style.AtlasManager|undefined} atlasManager
*/
ol.style.RegularShape.prototype.render_ = function(atlasManager) {
var imageSize;
var lineDash = null;
var strokeStyle;
var strokeWidth = 0;
if (!goog.isNull(this.stroke_)) {
strokeStyle = ol.color.asString(this.stroke_.getColor());
strokeWidth = this.stroke_.getWidth();
if (!goog.isDef(strokeWidth)) {
strokeWidth = ol.render.canvas.defaultLineWidth;
}
lineDash = this.stroke_.getLineDash();
if (!ol.has.CANVAS_LINE_DASH) {
lineDash = null;
}
}
var size = 2 * (this.radius_ + strokeWidth) + 1;
// draw the regular shape on the canvas
/** @type {ol.style.RegularShape.RenderOptions} */
var renderOptions = {
strokeStyle: strokeStyle,
strokeWidth: strokeWidth,
size: size,
lineDash: lineDash
};
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;
size = this.canvas_.width;
imageSize = size;
var context = /** @type {CanvasRenderingContext2D} */
(canvas.getContext('2d'));
(this.canvas_.getContext('2d'));
this.draw_(renderOptions, context, 0, 0);
this.createHitDetectionCanvas_(renderOptions);
} else {
// an atlas manager is used, add the symbol to an atlas
size = Math.round(size);
var hasCustomHitDetectionImage = goog.isNull(this.fill_);
var renderHitDetectionCallback;
if (hasCustomHitDetectionImage) {
// render the hit-detection image into a separate atlas image
renderHitDetectionCallback =
goog.bind(this.drawHitDetectionCanvas_, this, renderOptions);
}
var id = this.getChecksum();
var info = atlasManager.add(
id, size, size, goog.bind(this.draw_, this, renderOptions),
renderHitDetectionCallback);
goog.asserts.assert(!goog.isNull(info), 'shape size is too large');
this.canvas_ = info.image;
this.origin_ = [info.offsetX, info.offsetY];
imageSize = info.image.width;
if (hasCustomHitDetectionImage) {
this.hitDetectionCanvas_ = info.hitImage;
this.hitDetectionOrigin_ = [info.hitOffsetX, info.hitOffsetY];
this.hitDetectionImageSize_ =
[info.hitImage.width, info.hitImage.height];
} else {
this.hitDetectionCanvas_ = this.canvas_;
this.hitDetectionOrigin_ = this.origin_;
this.hitDetectionImageSize_ = [imageSize, imageSize];
}
}
this.anchor_ = [size / 2, size / 2];
this.size_ = [size, size];
this.imageSize_ = [imageSize, imageSize];
};
/**
* @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.RegularShape.prototype.draw_ = function(renderOptions, context, x, y) {
var i, angle0, radiusC;
// reset transform
context.setTransform(1, 0, 0, 1, 0, 0);
// then move to (x, y)
context.translate(x, y);
context.beginPath();
if (this.radius2_ !== this.radius_) {
this.points_ = 2 * this.points_;
@@ -264,8 +392,8 @@ ol.style.RegularShape.prototype.render_ = function() {
for (i = 0; i <= this.points_; i++) {
angle0 = i * 2 * Math.PI / this.points_ - Math.PI / 2 + this.angle_;
radiusC = i % 2 === 0 ? this.radius_ : this.radius2_;
context.lineTo(size / 2 + radiusC * Math.cos(angle0),
size / 2 + radiusC * Math.sin(angle0));
context.lineTo(renderOptions.size / 2 + radiusC * Math.cos(angle0),
renderOptions.size / 2 + radiusC * Math.sin(angle0));
}
if (!goog.isNull(this.fill_)) {
@@ -273,52 +401,111 @@ ol.style.RegularShape.prototype.render_ = function() {
context.fill();
}
if (!goog.isNull(this.stroke_)) {
context.strokeStyle = strokeStyle;
lineDash = this.stroke_.getLineDash();
if (ol.has.CANVAS_LINE_DASH && !goog.isNull(lineDash)) {
context.setLineDash(lineDash);
context.strokeStyle = renderOptions.strokeStyle;
context.lineWidth = renderOptions.strokeWidth;
if (!goog.isNull(renderOptions.lineDash)) {
context.setLineDash(renderOptions.lineDash);
}
context.lineWidth = strokeWidth;
context.stroke();
}
context.closePath();
};
// deal with the hit detection canvas
/**
* @private
* @param {ol.style.RegularShape.RenderOptions} renderOptions
*/
ol.style.RegularShape.prototype.createHitDetectionCanvas_ =
function(renderOptions) {
this.hitDetectionImageSize_ = [renderOptions.size, renderOptions.size];
if (!goog.isNull(this.fill_)) {
this.hitDetectionCanvas_ = canvas;
} else {
this.hitDetectionCanvas_ = this.canvas_;
return;
}
// if no fill style is set, create an extra hit-detection image with a
// default fill style
this.hitDetectionCanvas_ = /** @type {HTMLCanvasElement} */
(goog.dom.createElement(goog.dom.TagName.CANVAS));
canvas = this.hitDetectionCanvas_;
var canvas = this.hitDetectionCanvas_;
canvas.height = size;
canvas.width = size;
canvas.height = renderOptions.size;
canvas.width = renderOptions.size;
context = /** @type {CanvasRenderingContext2D} */
var context = /** @type {CanvasRenderingContext2D} */
(canvas.getContext('2d'));
this.drawHitDetectionCanvas_(renderOptions, context, 0, 0);
};
/**
* @private
* @param {ol.style.RegularShape.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.RegularShape.prototype.drawHitDetectionCanvas_ =
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();
if (this.radius2_ !== this.radius_) {
this.points_ = 2 * this.points_;
}
var i, radiusC, angle0;
for (i = 0; i <= this.points_; i++) {
angle0 = i * 2 * Math.PI / this.points_ - Math.PI / 2 + this.angle_;
radiusC = i % 2 === 0 ? this.radius_ : this.radius2_;
context.lineTo(size / 2 + radiusC * Math.cos(angle0),
size / 2 + radiusC * Math.sin(angle0));
context.lineTo(renderOptions.size / 2 + radiusC * Math.cos(angle0),
renderOptions.size / 2 + radiusC * Math.sin(angle0));
}
context.fillStyle = ol.render.canvas.defaultFillStyle;
context.fill();
if (!goog.isNull(this.stroke_)) {
context.strokeStyle = strokeStyle;
lineDash = this.stroke_.getLineDash();
if (ol.has.CANVAS_LINE_DASH && !goog.isNull(lineDash)) {
context.setLineDash(lineDash);
context.strokeStyle = renderOptions.strokeStyle;
context.lineWidth = renderOptions.strokeWidth;
if (!goog.isNull(renderOptions.lineDash)) {
context.setLineDash(renderOptions.lineDash);
}
context.lineWidth = strokeWidth;
context.stroke();
}
context.closePath();
};
/**
* @inheritDoc
*/
ol.style.RegularShape.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] ||
this.radius2_ != this.checksums_[4] ||
this.angle_ != this.checksums_[5] ||
this.points_ != this.checksums_[6]);
if (recalculate) {
var checksum = 'r' + strokeChecksum + fillChecksum +
(goog.isDef(this.radius_) ? this.radius_.toString() : '-') +
(goog.isDef(this.radius2_) ? this.radius2_.toString() : '-') +
(goog.isDef(this.angle_) ? this.angle_.toString() : '-') +
(goog.isDef(this.points_) ? this.points_.toString() : '-');
this.checksums_ = [checksum, strokeChecksum, fillChecksum,
this.radius_, this.radius2_, this.angle_, this.points_];
}
return size;
return this.checksums_[0];
};

View File

@@ -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 {string|undefined}
*/
this.checksum_ = undefined;
};
@@ -117,6 +129,7 @@ ol.style.Stroke.prototype.getWidth = function() {
*/
ol.style.Stroke.prototype.setColor = function(color) {
this.color_ = color;
this.checksum_ = undefined;
};
@@ -128,6 +141,7 @@ ol.style.Stroke.prototype.setColor = function(color) {
*/
ol.style.Stroke.prototype.setLineCap = function(lineCap) {
this.lineCap_ = lineCap;
this.checksum_ = undefined;
};
@@ -139,6 +153,7 @@ ol.style.Stroke.prototype.setLineCap = function(lineCap) {
*/
ol.style.Stroke.prototype.setLineDash = function(lineDash) {
this.lineDash_ = lineDash;
this.checksum_ = undefined;
};
@@ -150,6 +165,7 @@ ol.style.Stroke.prototype.setLineDash = function(lineDash) {
*/
ol.style.Stroke.prototype.setLineJoin = function(lineJoin) {
this.lineJoin_ = lineJoin;
this.checksum_ = undefined;
};
@@ -161,6 +177,7 @@ ol.style.Stroke.prototype.setLineJoin = function(lineJoin) {
*/
ol.style.Stroke.prototype.setMiterLimit = function(miterLimit) {
this.miterLimit_ = miterLimit;
this.checksum_ = undefined;
};
@@ -172,4 +189,33 @@ ol.style.Stroke.prototype.setMiterLimit = function(miterLimit) {
*/
ol.style.Stroke.prototype.setWidth = function(width) {
this.width_ = width;
this.checksum_ = undefined;
};
/**
* @inheritDoc
*/
ol.style.Stroke.prototype.getChecksum = function() {
if (!goog.isDef(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_;
};

56
src/ol/webgl/buffer.js Normal file
View File

@@ -0,0 +1,56 @@
goog.provide('ol.webgl.Buffer');
goog.require('goog.array');
goog.require('goog.webgl');
goog.require('ol');
/**
* @enum {number}
*/
ol.webgl.BufferUsage = {
STATIC_DRAW: goog.webgl.STATIC_DRAW,
STREAM_DRAW: goog.webgl.STREAM_DRAW,
DYNAMIC_DRAW: goog.webgl.DYNAMIC_DRAW
};
/**
* @constructor
* @param {Array.<number>=} opt_arr Array.
* @param {number=} opt_usage Usage.
* @struct
*/
ol.webgl.Buffer = function(opt_arr, opt_usage) {
/**
* @private
* @type {Array.<number>}
*/
this.arr_ = goog.isDef(opt_arr) ? opt_arr : [];
/**
* @private
* @type {number}
*/
this.usage_ = goog.isDef(opt_usage) ?
opt_usage : ol.webgl.BufferUsage.STATIC_DRAW;
};
/**
* @return {Array.<number>} Array.
*/
ol.webgl.Buffer.prototype.getArray = function() {
return this.arr_;
};
/**
* @return {number} Usage.
*/
ol.webgl.Buffer.prototype.getUsage = function() {
return this.usage_;
};

View File

@@ -1,18 +1,18 @@
goog.provide('ol.webgl.Context');
goog.require('goog.array');
goog.require('goog.asserts');
goog.require('goog.events');
goog.require('goog.log');
goog.require('goog.object');
goog.require('ol.structs.Buffer');
goog.require('ol.structs.IntegerSet');
goog.require('ol');
goog.require('ol.webgl.Buffer');
goog.require('ol.webgl.WebGLContextEventType');
/**
* @typedef {{buf: ol.structs.Buffer,
* buffer: WebGLBuffer,
* dirtySet: ol.structs.IntegerSet}}
* @typedef {{buf: ol.webgl.Buffer,
* buffer: WebGLBuffer}}
*/
ol.webgl.BufferCacheEntry;
@@ -66,6 +66,18 @@ ol.webgl.Context = function(canvas, gl) {
*/
this.currentProgram_ = null;
/**
* @type {boolean}
*/
this.hasOESElementIndexUint = goog.array.contains(
ol.WEBGL_EXTENSIONS, 'OES_element_index_uint');
// use the OES_element_index_uint extension if available
if (this.hasOESElementIndexUint) {
var ext = gl.getExtension('OES_element_index_uint');
goog.asserts.assert(!goog.isNull(ext));
}
goog.events.listen(this.canvas_, ol.webgl.WebGLContextEventType.LOST,
this.handleWebGLContextLost, false, this);
goog.events.listen(this.canvas_, ol.webgl.WebGLContextEventType.RESTORED,
@@ -75,8 +87,11 @@ ol.webgl.Context = function(canvas, gl) {
/**
* Just bind the buffer if it's in the cache. Otherwise create
* the WebGL buffer, bind it, populate it, and add an entry to
* the cache.
* @param {number} target Target.
* @param {ol.structs.Buffer} buf Buffer.
* @param {ol.webgl.Buffer} buf Buffer.
*/
ol.webgl.Context.prototype.bindBuffer = function(target, buf) {
var gl = this.getGL();
@@ -85,45 +100,37 @@ ol.webgl.Context.prototype.bindBuffer = function(target, buf) {
if (bufferKey in this.bufferCache_) {
var bufferCacheEntry = this.bufferCache_[bufferKey];
gl.bindBuffer(target, bufferCacheEntry.buffer);
bufferCacheEntry.dirtySet.forEachRange(function(start, stop) {
// FIXME check if slice is really efficient here
var slice = arr.slice(start, stop);
gl.bufferSubData(
target,
start,
target == goog.webgl.ARRAY_BUFFER ?
new Float32Array(slice) :
new Uint16Array(slice));
});
bufferCacheEntry.dirtySet.clear();
} else {
var buffer = gl.createBuffer();
gl.bindBuffer(target, buffer);
gl.bufferData(
target,
target == goog.webgl.ARRAY_BUFFER ?
new Float32Array(arr) : new Uint16Array(arr),
buf.getUsage());
var dirtySet = new ol.structs.IntegerSet();
buf.addDirtySet(dirtySet);
goog.asserts.assert(target == goog.webgl.ARRAY_BUFFER ||
target == goog.webgl.ELEMENT_ARRAY_BUFFER);
var /** @type {ArrayBufferView} */ arrayBuffer;
if (target == goog.webgl.ARRAY_BUFFER) {
arrayBuffer = new Float32Array(arr);
} else if (target == goog.webgl.ELEMENT_ARRAY_BUFFER) {
arrayBuffer = this.hasOESElementIndexUint ?
new Uint32Array(arr) : new Uint16Array(arr);
} else {
goog.asserts.fail();
}
gl.bufferData(target, arrayBuffer, buf.getUsage());
this.bufferCache_[bufferKey] = {
buf: buf,
buffer: buffer,
dirtySet: dirtySet
buffer: buffer
};
}
};
/**
* @param {ol.structs.Buffer} buf Buffer.
* @param {ol.webgl.Buffer} buf Buffer.
*/
ol.webgl.Context.prototype.deleteBuffer = function(buf) {
var gl = this.getGL();
var bufferKey = goog.getUid(buf);
goog.asserts.assert(bufferKey in this.bufferCache_);
var bufferCacheEntry = this.bufferCache_[bufferKey];
bufferCacheEntry.buf.removeDirtySet(bufferCacheEntry.dirtySet);
if (!gl.isContextLost()) {
gl.deleteBuffer(bufferCacheEntry.buffer);
}
@@ -135,9 +142,6 @@ ol.webgl.Context.prototype.deleteBuffer = function(buf) {
* @inheritDoc
*/
ol.webgl.Context.prototype.disposeInternal = function() {
goog.object.forEach(this.bufferCache_, function(bufferCacheEntry) {
bufferCacheEntry.buf.removeDirtySet(bufferCacheEntry.dirtySet);
});
var gl = this.getGL();
if (!gl.isContextLost()) {
goog.object.forEach(this.bufferCache_, function(bufferCacheEntry) {
@@ -171,6 +175,8 @@ ol.webgl.Context.prototype.getGL = function() {
/**
* Get shader from the cache if it's in the cache. Otherwise, create
* the WebGL shader, compile it, and add entry to cache.
* @param {ol.webgl.Shader} shaderObject Shader object.
* @return {WebGLShader} Shader.
*/
@@ -199,6 +205,9 @@ ol.webgl.Context.prototype.getShader = function(shaderObject) {
/**
* Get the program from the cache if it's in the cache. Otherwise create
* the WebGL program, attach the shaders to it, and add an entry to the
* cache.
* @param {ol.webgl.shader.Fragment} fragmentShaderObject Fragment shader.
* @param {ol.webgl.shader.Vertex} vertexShaderObject Vertex shader.
* @return {WebGLProgram} Program.
@@ -249,6 +258,9 @@ ol.webgl.Context.prototype.handleWebGLContextRestored = function() {
/**
* Just return false if that program is used already. Other use
* that program (call `gl.useProgram`) and make it the "current
* program".
* @param {WebGLProgram} program Program.
* @return {boolean} Changed.
* @api

View File

@@ -0,0 +1,158 @@
goog.provide('ol.test.render.webgl.Replay');
describe('ol.render.webgl.ImageReplay', function() {
var replay;
var createImageStyle = function(image) {
var imageStyle = new ol.style.Image({
opacity: 0.1,
rotateWithView: true,
rotation: 1.5,
scale: 2.0
});
imageStyle.getAnchor = function() {
return [0.5, 1];
};
imageStyle.getImage = function() {
return image;
};
imageStyle.getImageSize = function() {
return [512, 512];
};
imageStyle.getOrigin = function() {
return [200, 200];
};
imageStyle.getSize = function() {
return [256, 256];
};
return imageStyle;
};
beforeEach(function() {
var tolerance = 0.1;
var maxExtent = [-10000, -20000, 10000, 20000];
replay = new ol.render.webgl.ImageReplay(tolerance, maxExtent);
});
describe('#setImageStyle', function() {
var imageStyle1, imageStyle2;
beforeEach(function() {
imageStyle1 = createImageStyle(new Image());
imageStyle2 = createImageStyle(new Image());
});
it('set expected states', function() {
replay.setImageStyle(imageStyle1);
expect(replay.anchorX_).to.be(0.5);
expect(replay.anchorY_).to.be(1);
expect(replay.height_).to.be(256);
expect(replay.imageHeight_).to.be(512);
expect(replay.imageWidth_).to.be(512);
expect(replay.opacity_).to.be(0.1);
expect(replay.originX_).to.be(200);
expect(replay.originY_).to.be(200);
expect(replay.rotation_).to.be(1.5);
expect(replay.rotateWithView_).to.be(true);
expect(replay.scale_).to.be(2.0);
expect(replay.width_).to.be(256);
expect(replay.images_).to.have.length(1);
expect(replay.groupIndices_).to.have.length(0);
replay.setImageStyle(imageStyle1);
expect(replay.images_).to.have.length(1);
expect(replay.groupIndices_).to.have.length(0);
replay.setImageStyle(imageStyle2);
expect(replay.images_).to.have.length(2);
expect(replay.groupIndices_).to.have.length(1);
});
});
describe('#drawPointGeometry', function() {
beforeEach(function() {
var imageStyle = createImageStyle(new Image());
replay.setImageStyle(imageStyle);
});
it('sets the buffer data', function() {
var point;
point = new ol.geom.Point([1000, 2000]);
replay.drawPointGeometry(point, null);
expect(replay.vertices_).to.have.length(32);
expect(replay.indices_).to.have.length(6);
expect(replay.indices_[0]).to.be(0);
expect(replay.indices_[1]).to.be(1);
expect(replay.indices_[2]).to.be(2);
expect(replay.indices_[3]).to.be(0);
expect(replay.indices_[4]).to.be(2);
expect(replay.indices_[5]).to.be(3);
point = new ol.geom.Point([2000, 3000]);
replay.drawPointGeometry(point, null);
expect(replay.vertices_).to.have.length(64);
expect(replay.indices_).to.have.length(12);
expect(replay.indices_[6]).to.be(4);
expect(replay.indices_[7]).to.be(5);
expect(replay.indices_[8]).to.be(6);
expect(replay.indices_[9]).to.be(4);
expect(replay.indices_[10]).to.be(6);
expect(replay.indices_[11]).to.be(7);
});
});
describe('#drawMultiPointGeometry', function() {
beforeEach(function() {
var imageStyle = createImageStyle(new Image());
replay.setImageStyle(imageStyle);
});
it('sets the buffer data', function() {
var multiPoint;
multiPoint = new ol.geom.MultiPoint(
[[1000, 2000], [2000, 3000]]);
replay.drawMultiPointGeometry(multiPoint, null);
expect(replay.vertices_).to.have.length(64);
expect(replay.indices_).to.have.length(12);
expect(replay.indices_[0]).to.be(0);
expect(replay.indices_[1]).to.be(1);
expect(replay.indices_[2]).to.be(2);
expect(replay.indices_[3]).to.be(0);
expect(replay.indices_[4]).to.be(2);
expect(replay.indices_[5]).to.be(3);
expect(replay.indices_[6]).to.be(4);
expect(replay.indices_[7]).to.be(5);
expect(replay.indices_[8]).to.be(6);
expect(replay.indices_[9]).to.be(4);
expect(replay.indices_[10]).to.be(6);
expect(replay.indices_[11]).to.be(7);
multiPoint = new ol.geom.MultiPoint(
[[3000, 4000], [4000, 5000]]);
replay.drawMultiPointGeometry(multiPoint, null);
expect(replay.vertices_).to.have.length(128);
expect(replay.indices_).to.have.length(24);
expect(replay.indices_[12]).to.be(8);
expect(replay.indices_[13]).to.be(9);
expect(replay.indices_[14]).to.be(10);
expect(replay.indices_[15]).to.be(8);
expect(replay.indices_[16]).to.be(10);
expect(replay.indices_[17]).to.be(11);
expect(replay.indices_[18]).to.be(12);
expect(replay.indices_[19]).to.be(13);
expect(replay.indices_[20]).to.be(14);
expect(replay.indices_[21]).to.be(12);
expect(replay.indices_[22]).to.be(14);
expect(replay.indices_[23]).to.be(15);
});
});
});
goog.require('ol.extent');
goog.require('ol.geom.MultiPoint');
goog.require('ol.geom.Point');
goog.require('ol.render.webgl.ImageReplay');
goog.require('ol.style.Image');

View File

@@ -1,297 +0,0 @@
goog.provide('ol.test.structs.Buffer');
describe('ol.structs.Buffer', function() {
describe('constructor', function() {
describe('without an argument', function() {
var b;
beforeEach(function() {
b = new ol.structs.Buffer();
});
it('constructs an empty instance', function() {
expect(b.getArray()).to.be.empty();
expect(b.getCount()).to.be(0);
});
});
describe('with a single array argument', function() {
var b;
beforeEach(function() {
b = new ol.structs.Buffer([0, 1, 2, 3]);
});
it('constructs a populated instance', function() {
expect(b.getArray()).to.eql([0, 1, 2, 3]);
});
});
});
describe('with an empty instance', function() {
var b;
beforeEach(function() {
b = new ol.structs.Buffer();
});
describe('forEachRange', function() {
it('does not call the callback', function() {
var callback = sinon.spy();
b.forEachRange(callback);
expect(callback).not.to.be.called();
});
});
describe('getArray', function() {
it('returns an empty array', function() {
expect(b.getArray()).to.be.empty();
});
});
describe('getCount', function() {
it('returns 0', function() {
expect(b.getCount()).to.be(0);
});
});
});
describe('with an empty instance with spare capacity', function() {
var b;
beforeEach(function() {
b = new ol.structs.Buffer(new Array(4), 0);
});
describe('add', function() {
it('allows elements to be added', function() {
expect(b.add([0, 1, 2, 3])).to.be(0);
expect(b.getArray()).to.eql([0, 1, 2, 3]);
});
});
describe('forEachRange', function() {
it('does not call the callback', function() {
var callback = sinon.spy();
b.forEachRange(callback);
expect(callback).not.to.be.called();
});
});
describe('getCount', function() {
it('returns 0', function() {
expect(b.getCount()).to.be(0);
});
});
});
describe('with an instance with no spare capacity', function() {
var b;
beforeEach(function() {
b = new ol.structs.Buffer([0, 1, 2, 3]);
});
describe('add', function() {
it('throws an exception', function() {
expect(function() {
b.add([4, 5]);
}).to.throwException();
});
});
describe('forEachRange', function() {
it('calls the callback', function() {
var callback = sinon.spy();
b.forEachRange(callback);
expect(callback.calledOnce).to.be(true);
expect(callback.args[0]).to.eql([0, 4]);
});
});
describe('getCount', function() {
it('returns the expected value', function() {
expect(b.getCount()).to.be(4);
});
});
describe('remove', function() {
it('allows items to be removes', function() {
expect(function() {
b.remove(4, 2);
}).to.not.throwException();
});
});
describe('set', function() {
it('updates the items', function() {
b.set([5, 6], 2);
expect(b.getArray()).to.eql([0, 1, 5, 6]);
});
it('marks the set items as dirty', function() {
var dirtySet = new ol.structs.IntegerSet();
b.addDirtySet(dirtySet);
expect(dirtySet.isEmpty()).to.be(true);
b.set([5, 6], 2);
expect(dirtySet.isEmpty()).to.be(false);
expect(dirtySet.getArray()).to.eql([2, 4]);
});
});
});
describe('with an instance with spare capacity', function() {
var b;
beforeEach(function() {
var arr = [0, 1, 2, 3];
arr.length = 8;
b = new ol.structs.Buffer(arr, 4);
});
describe('add', function() {
it('allows more items to be added', function() {
expect(b.add([4, 5, 6, 7])).to.be(4);
expect(b.getArray()).to.eql([0, 1, 2, 3, 4, 5, 6, 7]);
});
});
describe('forEachRange', function() {
it('calls the callback with the expected values', function() {
var callback = sinon.spy();
b.forEachRange(callback);
expect(callback.calledOnce).to.be(true);
expect(callback.args[0]).to.eql([0, 4]);
});
});
describe('getCount', function() {
it('returns the expected value', function() {
expect(b.getCount()).to.be(4);
});
});
describe('getFreeSet', function() {
it('returns the expected set', function() {
var freeSet = b.getFreeSet();
expect(freeSet.isEmpty()).to.be(false);
expect(freeSet.getArray()).to.eql([4, 8]);
});
});
});
describe('with a populated instance', function() {
var b;
beforeEach(function() {
b = new ol.structs.Buffer([1234567.1234567, -7654321.7654321]);
});
describe('getSplit32', function() {
it('returns the expected value', function() {
var split32 = b.getSplit32();
expect(split32).to.be.a(Float32Array);
expect(split32).to.have.length(4);
expect(split32[0]).to.roughlyEqual(1179648.0, 1e1);
expect(split32[1]).to.roughlyEqual(54919.12345670001, 1e-2);
expect(split32[2]).to.roughlyEqual(-7602176.0, 1e1);
expect(split32[3]).to.roughlyEqual(-52145.76543209981, 1e-2);
});
it('tracks updates', function() {
b.getSplit32();
b.getArray()[0] = 0;
b.markDirty(1, 0);
var split32 = b.getSplit32();
expect(split32).to.be.a(Float32Array);
expect(split32).to.have.length(4);
expect(split32[0]).to.be(0);
expect(split32[1]).to.be(0);
expect(split32[2]).to.roughlyEqual(-7602176.0, 1e1);
expect(split32[3]).to.roughlyEqual(-52145.76543209981, 1e-2);
});
});
});
describe('usage tests', function() {
it('allows multiple adds and removes', function() {
var b = new ol.structs.Buffer(new Array(8), 0);
expect(b.add([0, 1])).to.be(0);
expect(b.getArray()).to.arreqlNaN([0, 1, NaN, NaN, NaN, NaN, NaN, NaN]);
expect(b.getCount()).to.be(2);
expect(b.add([2, 3, 4, 5])).to.be(2);
expect(b.getArray()).to.arreqlNaN([0, 1, 2, 3, 4, 5, NaN, NaN]);
expect(b.getCount()).to.be(6);
expect(b.add([6, 7])).to.be(6);
expect(b.getArray()).to.eql([0, 1, 2, 3, 4, 5, 6, 7]);
expect(b.getCount()).to.be(8);
b.remove(2, 2);
expect(b.getArray()).to.arreqlNaN([0, 1, NaN, NaN, 4, 5, 6, 7]);
expect(b.getCount()).to.be(6);
expect(b.add([8, 9])).to.be(2);
expect(b.getArray()).to.eql([0, 1, 8, 9, 4, 5, 6, 7]);
expect(b.getCount()).to.be(8);
b.remove(1, 1);
expect(b.getArray()).to.arreqlNaN([0, NaN, 8, 9, 4, 5, 6, 7]);
expect(b.getCount()).to.be(7);
b.remove(4, 4);
expect(b.getArray()).to.arreqlNaN([0, NaN, 8, 9, NaN, NaN, NaN, NaN]);
expect(b.getCount()).to.be(3);
expect(b.add([10, 11, 12])).to.be(4);
expect(b.getArray()).to.arreqlNaN([0, NaN, 8, 9, 10, 11, 12, NaN]);
expect(b.getCount()).to.be(6);
expect(b.add([13])).to.be(1);
expect(b.getArray()).to.arreqlNaN([0, 13, 8, 9, 10, 11, 12, NaN]);
expect(b.getCount()).to.be(7);
});
});
});
goog.require('ol.structs.Buffer');
goog.require('ol.structs.IntegerSet');

View File

@@ -1,622 +0,0 @@
goog.provide('ol.test.structs.IntegerSet');
describe('ol.structs.IntegerSet', function() {
describe('constructor', function() {
describe('without an argument', function() {
it('constructs an empty instance', function() {
var is = new ol.structs.IntegerSet();
expect(is).to.be.an(ol.structs.IntegerSet);
expect(is.getArray()).to.be.empty();
});
});
describe('with an argument', function() {
it('constructs with a valid array', function() {
var is = new ol.structs.IntegerSet([0, 2, 4, 6]);
expect(is).to.be.an(ol.structs.IntegerSet);
expect(is.getArray()).to.eql([0, 2, 4, 6]);
});
it('throws an exception with an odd number of elements', function() {
expect(function() {
var is = new ol.structs.IntegerSet([0, 2, 4]);
is = is; // suppress gjslint warning about unused variable
}).to.throwException();
});
it('throws an exception with out-of-order elements', function() {
expect(function() {
var is = new ol.structs.IntegerSet([0, 2, 2, 4]);
is = is; // suppress gjslint warning about unused variable
}).to.throwException();
});
});
});
describe('with an empty instance', function() {
var is;
beforeEach(function() {
is = new ol.structs.IntegerSet();
});
describe('addRange', function() {
it('creates a new element', function() {
is.addRange(0, 2);
expect(is.getArray()).to.eql([0, 2]);
});
});
describe('findRange', function() {
it('returns -1', function() {
expect(is.findRange(2)).to.be(-1);
});
});
describe('forEachRange', function() {
it('does not call the callback', function() {
var callback = sinon.spy();
is.forEachRange(callback);
expect(callback).to.not.be.called();
});
});
describe('forEachRangeInverted', function() {
it('does call the callback', function() {
var callback = sinon.spy();
is.forEachRangeInverted(0, 8, callback);
expect(callback.calledOnce).to.be(true);
expect(callback.args[0]).to.eql([0, 8]);
});
});
describe('getFirst', function() {
it('returns -1', function() {
expect(is.getFirst()).to.be(-1);
});
});
describe('getLast', function() {
it('returns -1', function() {
expect(is.getLast()).to.be(-1);
});
});
describe('getSize', function() {
it('returns 0', function() {
expect(is.getSize()).to.be(0);
});
});
describe('intersectsRange', function() {
it('returns false', function() {
expect(is.intersectsRange(0, 0)).to.be(false);
});
});
describe('isEmpty', function() {
it('returns true', function() {
expect(is.isEmpty()).to.be(true);
});
});
describe('toString', function() {
it('returns an empty string', function() {
expect(is.toString()).to.be.empty();
});
});
});
describe('with a populated instance', function() {
var is;
beforeEach(function() {
is = new ol.structs.IntegerSet([4, 6, 8, 10, 12, 14]);
});
describe('addRange', function() {
it('inserts before the first element', function() {
is.addRange(0, 2);
expect(is.getArray()).to.eql([0, 2, 4, 6, 8, 10, 12, 14]);
});
it('extends the first element to the left', function() {
is.addRange(0, 4);
expect(is.getArray()).to.eql([0, 6, 8, 10, 12, 14]);
});
it('extends the first element to the right', function() {
is.addRange(6, 7);
expect(is.getArray()).to.eql([4, 7, 8, 10, 12, 14]);
});
it('merges the first two elements', function() {
is.addRange(6, 8);
expect(is.getArray()).to.eql([4, 10, 12, 14]);
});
it('extends middle elements to the left', function() {
is.addRange(7, 8);
expect(is.getArray()).to.eql([4, 6, 7, 10, 12, 14]);
});
it('extends middle elements to the right', function() {
is.addRange(10, 11);
expect(is.getArray()).to.eql([4, 6, 8, 11, 12, 14]);
});
it('merges the last two elements', function() {
is.addRange(10, 12);
expect(is.getArray()).to.eql([4, 6, 8, 14]);
});
it('extends the last element to the left', function() {
is.addRange(11, 12);
expect(is.getArray()).to.eql([4, 6, 8, 10, 11, 14]);
});
it('extends the last element to the right', function() {
is.addRange(14, 15);
expect(is.getArray()).to.eql([4, 6, 8, 10, 12, 15]);
});
it('inserts after the last element', function() {
is.addRange(16, 18);
expect(is.getArray()).to.eql([4, 6, 8, 10, 12, 14, 16, 18]);
});
});
describe('clear', function() {
it('clears the instance', function() {
is.clear();
expect(is.getArray()).to.be.empty();
});
});
describe('findRange', function() {
it('throws an exception when passed a negative size', function() {
expect(function() {
is.findRange(-1);
}).to.throwException();
});
it('throws an exception when passed a zero size', function() {
expect(function() {
is.findRange(0);
}).to.throwException();
});
it('finds the first range of size 1', function() {
expect(is.findRange(1)).to.be(4);
});
it('finds the first range of size 2', function() {
expect(is.findRange(2)).to.be(4);
});
it('returns -1 when no range can be found', function() {
expect(is.findRange(3)).to.be(-1);
});
});
describe('forEachRange', function() {
it('calls the callback', function() {
var callback = sinon.spy();
is.forEachRange(callback);
expect(callback).to.be.called();
expect(callback.calledThrice).to.be(true);
expect(callback.args[0]).to.eql([4, 6]);
expect(callback.args[1]).to.eql([8, 10]);
expect(callback.args[2]).to.eql([12, 14]);
});
});
describe('forEachRangeInverted', function() {
it('does call the callback', function() {
var callback = sinon.spy();
is.forEachRangeInverted(0, 16, callback);
expect(callback.callCount).to.be(4);
expect(callback.args[0]).to.eql([0, 4]);
expect(callback.args[1]).to.eql([6, 8]);
expect(callback.args[2]).to.eql([10, 12]);
expect(callback.args[3]).to.eql([14, 16]);
});
});
describe('getFirst', function() {
it('returns the expected value', function() {
expect(is.getFirst()).to.be(4);
});
});
describe('getLast', function() {
it('returns the expected value', function() {
expect(is.getLast()).to.be(14);
});
});
describe('getSize', function() {
it('returns the expected value', function() {
expect(is.getSize()).to.be(6);
});
});
describe('intersectsRange', function() {
it('returns the expected value for small ranges', function() {
expect(is.intersectsRange(1, 3)).to.be(false);
expect(is.intersectsRange(2, 4)).to.be(false);
expect(is.intersectsRange(3, 5)).to.be(true);
expect(is.intersectsRange(4, 6)).to.be(true);
expect(is.intersectsRange(5, 7)).to.be(true);
expect(is.intersectsRange(6, 8)).to.be(false);
expect(is.intersectsRange(7, 9)).to.be(true);
expect(is.intersectsRange(8, 10)).to.be(true);
expect(is.intersectsRange(9, 11)).to.be(true);
expect(is.intersectsRange(10, 12)).to.be(false);
expect(is.intersectsRange(11, 13)).to.be(true);
expect(is.intersectsRange(12, 14)).to.be(true);
expect(is.intersectsRange(13, 15)).to.be(true);
expect(is.intersectsRange(14, 16)).to.be(false);
expect(is.intersectsRange(15, 17)).to.be(false);
});
it('returns the expected value for large ranges', function() {
expect(is.intersectsRange(-3, 1)).to.be(false);
expect(is.intersectsRange(1, 5)).to.be(true);
expect(is.intersectsRange(1, 9)).to.be(true);
expect(is.intersectsRange(1, 13)).to.be(true);
expect(is.intersectsRange(1, 17)).to.be(true);
expect(is.intersectsRange(5, 9)).to.be(true);
expect(is.intersectsRange(5, 13)).to.be(true);
expect(is.intersectsRange(5, 17)).to.be(true);
expect(is.intersectsRange(9, 13)).to.be(true);
expect(is.intersectsRange(9, 17)).to.be(true);
expect(is.intersectsRange(13, 17)).to.be(true);
expect(is.intersectsRange(17, 21)).to.be(false);
});
});
describe('isEmpty', function() {
it('returns false', function() {
expect(is.isEmpty()).to.be(false);
});
});
describe('removeRange', function() {
it('removes the first part of the first element', function() {
is.removeRange(4, 5);
expect(is.getArray()).to.eql([5, 6, 8, 10, 12, 14]);
});
it('removes the last part of the first element', function() {
is.removeRange(5, 6);
expect(is.getArray()).to.eql([4, 5, 8, 10, 12, 14]);
});
it('removes the first element', function() {
is.removeRange(4, 6);
expect(is.getArray()).to.eql([8, 10, 12, 14]);
});
it('removes the first part of a middle element', function() {
is.removeRange(8, 9);
expect(is.getArray()).to.eql([4, 6, 9, 10, 12, 14]);
});
it('removes the last part of a middle element', function() {
is.removeRange(9, 10);
expect(is.getArray()).to.eql([4, 6, 8, 9, 12, 14]);
});
it('removes a middle element', function() {
is.removeRange(8, 10);
expect(is.getArray()).to.eql([4, 6, 12, 14]);
});
it('removes the first part of the last element', function() {
is.removeRange(12, 13);
expect(is.getArray()).to.eql([4, 6, 8, 10, 13, 14]);
});
it('removes the last part of the last element', function() {
is.removeRange(13, 14);
expect(is.getArray()).to.eql([4, 6, 8, 10, 12, 13]);
});
it('removes the last element', function() {
is.removeRange(12, 14);
expect(is.getArray()).to.eql([4, 6, 8, 10]);
});
it('can remove multiple ranges near the start', function() {
is.removeRange(3, 11);
expect(is.getArray()).to.eql([12, 14]);
});
it('can remove multiple ranges near the start', function() {
is.removeRange(7, 15);
expect(is.getArray()).to.eql([4, 6]);
});
it('throws an exception when passed an invalid range', function() {
expect(function() {
is.removeRange(2, 0);
}).to.throwException();
});
});
describe('toString', function() {
it('returns the expected value', function() {
expect(is.toString()).to.be('4-6, 8-10, 12-14');
});
});
});
describe('with fragmentation', function() {
var is;
beforeEach(function() {
is = new ol.structs.IntegerSet([0, 1, 2, 4, 5, 8, 9, 12, 13, 15, 16, 17]);
});
describe('findRange', function() {
it('finds the first range of size 1', function() {
expect(is.findRange(1)).to.be(0);
});
it('finds the first range of size 2', function() {
expect(is.findRange(2)).to.be(2);
});
it('finds the first range of size 3', function() {
expect(is.findRange(3)).to.be(5);
});
it('returns -1 when no range can be found', function() {
expect(is.findRange(4)).to.be(-1);
});
});
describe('getFirst', function() {
it('returns the expected value', function() {
expect(is.getFirst()).to.be(0);
});
});
describe('getLast', function() {
it('returns the expected value', function() {
expect(is.getLast()).to.be(17);
});
});
describe('getSize', function() {
it('returns the expected value', function() {
expect(is.getSize()).to.be(12);
});
});
describe('removeRange', function() {
it('removing an empty range has no effect', function() {
is.removeRange(0, 0);
expect(is.getArray()).to.eql(
[0, 1, 2, 4, 5, 8, 9, 12, 13, 15, 16, 17]);
});
it('can remove elements from the middle of range', function() {
is.removeRange(6, 7);
expect(is.getArray()).to.eql(
[0, 1, 2, 4, 5, 6, 7, 8, 9, 12, 13, 15, 16, 17]);
});
it('can remove multiple ranges', function() {
is.removeRange(2, 12);
expect(is.getArray()).to.eql([0, 1, 13, 15, 16, 17]);
});
it('can remove multiple ranges and reduce others', function() {
is.removeRange(0, 10);
expect(is.getArray()).to.eql([10, 12, 13, 15, 16, 17]);
});
it('can remove all ranges', function() {
is.removeRange(0, 18);
expect(is.getArray()).to.eql([]);
});
});
describe('toString', function() {
it('returns the expected value', function() {
expect(is.toString()).to.be('0-1, 2-4, 5-8, 9-12, 13-15, 16-17');
});
});
});
describe('compared to a slow reference implementation', function() {
var SimpleIntegerSet = function() {
this.integers_ = {};
};
SimpleIntegerSet.prototype.addRange = function(addStart, addStop) {
var i;
for (i = addStart; i < addStop; ++i) {
this.integers_[i.toString()] = true;
}
};
SimpleIntegerSet.prototype.clear = function() {
this.integers_ = {};
};
SimpleIntegerSet.prototype.getArray = function() {
var integers = goog.array.map(
goog.object.getKeys(this.integers_), Number);
goog.array.sort(integers);
var arr = [];
var start = -1, stop;
var i;
for (i = 0; i < integers.length; ++i) {
if (start == -1) {
start = stop = integers[i];
} else if (integers[i] == stop + 1) {
++stop;
} else {
arr.push(start, stop + 1);
start = stop = integers[i];
}
}
if (start != -1) {
arr.push(start, stop + 1);
}
return arr;
};
SimpleIntegerSet.prototype.removeRange = function(removeStart, removeStop) {
var i;
for (i = removeStart; i < removeStop; ++i) {
delete this.integers_[i.toString()];
}
};
var is, sis;
beforeEach(function() {
is = new ol.structs.IntegerSet();
sis = new SimpleIntegerSet();
});
it('behaves identically with random adds', function() {
var addStart, addStop, i;
for (i = 0; i < 64; ++i) {
addStart = goog.math.randomInt(128);
addStop = addStart + goog.math.randomInt(16);
is.addRange(addStart, addStop);
sis.addRange(addStart, addStop);
expect(is.getArray()).to.eql(sis.getArray());
}
});
it('behaves identically with random removes', function() {
is.addRange(0, 128);
sis.addRange(0, 128);
var i, removeStart, removeStop;
for (i = 0; i < 64; ++i) {
removeStart = goog.math.randomInt(128);
removeStop = removeStart + goog.math.randomInt(16);
is.removeRange(removeStart, removeStop);
sis.removeRange(removeStart, removeStop);
expect(is.getArray()).to.eql(sis.getArray());
}
});
it('behaves identically with random adds and removes', function() {
var i, start, stop;
for (i = 0; i < 64; ++i) {
start = goog.math.randomInt(128);
stop = start + goog.math.randomInt(16);
if (Math.random() < 0.5) {
is.addRange(start, stop);
sis.addRange(start, stop);
} else {
is.removeRange(start, stop);
sis.removeRange(start, stop);
}
expect(is.getArray()).to.eql(sis.getArray());
}
});
it('behaves identically with random adds, removes, and clears', function() {
var i, p, start, stop;
for (i = 0; i < 64; ++i) {
start = goog.math.randomInt(128);
stop = start + goog.math.randomInt(16);
p = Math.random();
if (p < 0.45) {
is.addRange(start, stop);
sis.addRange(start, stop);
} else if (p < 0.9) {
is.removeRange(start, stop);
sis.removeRange(start, stop);
} else {
is.clear();
sis.clear();
}
expect(is.getArray()).to.eql(sis.getArray());
}
});
});
});
goog.require('goog.array');
goog.require('goog.math');
goog.require('goog.object');
goog.require('ol.structs.IntegerSet');

View File

@@ -0,0 +1,273 @@
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({initialSize: 128});
var info = manager.add('1', 32, 32, defaultRender);
expect(info).to.eql({
offsetX: 1, offsetY: 1, image: manager.atlases_[0].canvas_,
hitOffsetX: undefined, hitOffsetY: undefined, hitImage: undefined});
expect(manager.getInfo('1')).to.eql(info);
});
it('adds one entry (also to the hit detection atlas)', function() {
var manager = new ol.style.AtlasManager({initialSize: 128});
var info = manager.add('1', 32, 32, defaultRender, defaultRender);
expect(info).to.eql({
offsetX: 1, offsetY: 1, image: manager.atlases_[0].canvas_,
hitOffsetX: 1, hitOffsetY: 1,
hitImage: manager.hitAtlases_[0].canvas_});
expect(manager.getInfo('1')).to.eql(info);
});
it('creates a new atlas if needed', function() {
var manager = new ol.style.AtlasManager({initialSize: 128});
expect(manager.add('1', 100, 100, defaultRender, defaultRender))
.to.be.ok();
var info = manager.add('2', 100, 100, defaultRender, defaultRender);
expect(info).to.be.ok();
expect(info.image.width).to.eql(256);
expect(manager.atlases_).to.have.length(2);
expect(info.hitImage.width).to.eql(256);
expect(manager.hitAtlases_).to.have.length(2);
});
it('creates new atlases until one is large enough', function() {
var manager = new ol.style.AtlasManager({initialSize: 128});
expect(manager.add('1', 100, 100, defaultRender, defaultRender))
.to.be.ok();
expect(manager.atlases_).to.have.length(1);
expect(manager.hitAtlases_).to.have.length(1);
var info = manager.add('2', 500, 500, defaultRender, defaultRender);
expect(info).to.be.ok();
expect(info.image.width).to.eql(512);
expect(manager.atlases_).to.have.length(3);
expect(info.hitImage.width).to.eql(512);
expect(manager.hitAtlases_).to.have.length(3);
});
it('checks all existing atlases and create a new if needed', function() {
var manager = new ol.style.AtlasManager({initialSize: 128});
expect(manager.add('1', 100, 100, defaultRender, defaultRender))
.to.be.ok();
expect(manager.add('2', 100, 100, defaultRender, defaultRender))
.to.be.ok();
expect(manager.atlases_).to.have.length(2);
expect(manager.hitAtlases_).to.have.length(2);
var info = manager.add(3, 500, 500, defaultRender, defaultRender);
expect(info).to.be.ok();
expect(info.image.width).to.eql(512);
expect(manager.atlases_).to.have.length(3);
expect(info.hitImage.width).to.eql(512);
expect(manager.hitAtlases_).to.have.length(3);
});
it('returns null if the size exceeds the maximum size', function() {
var manager = new ol.style.AtlasManager(
{initialSize: 128, maxSize: 2048});
expect(manager.add('1', 100, 100, defaultRender, defaultRender))
.to.be.ok();
expect(manager.add('2', 2048, 2048, defaultRender, defaultRender))
.to.eql(null);
});
});
describe('#getInfo', function() {
it('returns null if no entry for the given id', function() {
var manager = new ol.style.AtlasManager({initialSize: 128});
expect(manager.getInfo('123456')).to.eql(null);
});
});
});
goog.require('ol.style.Atlas');
goog.require('ol.style.AtlasManager');

View File

@@ -0,0 +1,240 @@
goog.provide('ol.test.style.Circle');
describe('ol.style.Circle', function() {
describe('#constructor', function() {
it('creates a canvas if no atlas is used (no fill-style)', 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]);
// hit-detection image is created, because no fill style is set
expect(style.getImage()).to.not.be(style.getHitDetectionImage());
expect(style.getHitDetectionImage()).to.be.an(HTMLCanvasElement);
expect(style.getHitDetectionImageSize()).to.eql([21, 21]);
expect(style.getHitDetectionOrigin()).to.eql([0, 0]);
});
it('creates a canvas if no atlas is used (fill-style)', function() {
var style = new ol.style.Circle({
radius: 10,
fill: new ol.style.Fill({
color: '#FFFF00'
})
});
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]);
// no hit-detection image is created, because fill style is set
expect(style.getImage()).to.be(style.getHitDetectionImage());
expect(style.getHitDetectionImage()).to.be.an(HTMLCanvasElement);
expect(style.getHitDetectionImageSize()).to.eql([21, 21]);
expect(style.getHitDetectionOrigin()).to.eql([0, 0]);
});
it('adds itself to an atlas manager (no fill-style)', function() {
var atlasManager = new ol.style.AtlasManager({initialSize: 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]);
// hit-detection image is created, because no fill style is set
expect(style.getImage()).to.not.be(style.getHitDetectionImage());
expect(style.getHitDetectionImage()).to.be.an(HTMLCanvasElement);
expect(style.getHitDetectionImageSize()).to.eql([512, 512]);
expect(style.getHitDetectionOrigin()).to.eql([1, 1]);
});
it('adds itself to an atlas manager (fill-style)', function() {
var atlasManager = new ol.style.AtlasManager({initialSize: 512});
var style = new ol.style.Circle({
radius: 10,
atlasManager: atlasManager,
fill: new ol.style.Fill({
color: '#FFFF00'
})
});
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]);
// no hit-detection image is created, because fill style is set
expect(style.getImage()).to.be(style.getHitDetectionImage());
expect(style.getHitDetectionImage()).to.be.an(HTMLCanvasElement);
expect(style.getHitDetectionImageSize()).to.eql([512, 512]);
expect(style.getHitDetectionOrigin()).to.eql([1, 1]);
});
});
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.AtlasManager');
goog.require('ol.style.Circle');
goog.require('ol.style.Fill');
goog.require('ol.style.Stroke');

View File

@@ -1,6 +1,9 @@
goog.provide('ol.test.style.RegularShape');
describe('ol.style.RegularShape', function() {
describe('#constructor', function() {
it('can use radius', function() {
var style = new ol.style.RegularShape({
radius: 5,
@@ -9,6 +12,7 @@ describe('ol.style.RegularShape', function() {
expect(style.getRadius()).to.eql(5);
expect(style.getRadius2()).to.eql(10);
});
it('can use radius1 as an alias for radius', function() {
var style = new ol.style.RegularShape({
radius1: 5,
@@ -17,6 +21,7 @@ describe('ol.style.RegularShape', function() {
expect(style.getRadius()).to.eql(5);
expect(style.getRadius2()).to.eql(10);
});
it('will use radius for radius2 if radius2 not defined', function() {
var style = new ol.style.RegularShape({
radius: 5
@@ -24,6 +29,7 @@ describe('ol.style.RegularShape', function() {
expect(style.getRadius()).to.eql(5);
expect(style.getRadius2()).to.eql(5);
});
it('will use radius1 for radius2 if radius2 not defined', function() {
var style = new ol.style.RegularShape({
radius1: 5
@@ -31,6 +37,261 @@ describe('ol.style.RegularShape', function() {
expect(style.getRadius()).to.eql(5);
expect(style.getRadius2()).to.eql(5);
});
it('creates a canvas if no atlas is used (no fill-style)', function() {
var style = new ol.style.RegularShape({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]);
// hit-detection image is created, because no fill style is set
expect(style.getImage()).to.not.be(style.getHitDetectionImage());
expect(style.getHitDetectionImage()).to.be.an(HTMLCanvasElement);
expect(style.getHitDetectionImageSize()).to.eql([21, 21]);
expect(style.getHitDetectionOrigin()).to.eql([0, 0]);
});
it('creates a canvas if no atlas is used (fill-style)', function() {
var style = new ol.style.RegularShape({
radius: 10,
fill: new ol.style.Fill({
color: '#FFFF00'
})
});
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]);
// no hit-detection image is created, because fill style is set
expect(style.getImage()).to.be(style.getHitDetectionImage());
expect(style.getHitDetectionImage()).to.be.an(HTMLCanvasElement);
expect(style.getHitDetectionImageSize()).to.eql([21, 21]);
expect(style.getHitDetectionOrigin()).to.eql([0, 0]);
});
it('adds itself to an atlas manager (no fill-style)', function() {
var atlasManager = new ol.style.AtlasManager({initialSize: 512});
var style = new ol.style.RegularShape(
{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]);
// hit-detection image is created, because no fill style is set
expect(style.getImage()).to.not.be(style.getHitDetectionImage());
expect(style.getHitDetectionImage()).to.be.an(HTMLCanvasElement);
expect(style.getHitDetectionImageSize()).to.eql([512, 512]);
expect(style.getHitDetectionOrigin()).to.eql([1, 1]);
});
it('adds itself to an atlas manager (fill-style)', function() {
var atlasManager = new ol.style.AtlasManager({initialSize: 512});
var style = new ol.style.RegularShape({
radius: 10,
atlasManager: atlasManager,
fill: new ol.style.Fill({
color: '#FFFF00'
})
});
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]);
// no hit-detection image is created, because fill style is set
expect(style.getImage()).to.be(style.getHitDetectionImage());
expect(style.getHitDetectionImage()).to.be.an(HTMLCanvasElement);
expect(style.getHitDetectionImageSize()).to.eql([512, 512]);
expect(style.getHitDetectionOrigin()).to.eql([1, 1]);
});
});
describe('#getChecksum', function() {
it('calculates not the same hash code (radius)', function() {
var style1 = new ol.style.RegularShape({
radius: 4,
radius2: 5
});
var style2 = new ol.style.RegularShape({
radius: 3,
radius2: 5
});
expect(style1.getChecksum()).to.not.eql(style2.getChecksum());
});
it('calculates not the same hash code (radius2)', function() {
var style1 = new ol.style.RegularShape({
radius: 4,
radius2: 5
});
var style2 = new ol.style.RegularShape({
radius: 4,
radius2: 6
});
expect(style1.getChecksum()).to.not.eql(style2.getChecksum());
});
it('calculates the same hash code (radius)', function() {
var style1 = new ol.style.RegularShape({
radius: 5
});
var style2 = new ol.style.RegularShape({
radius: 5
});
expect(style1.getChecksum()).to.eql(style2.getChecksum());
});
it('calculates not the same hash code (color)', function() {
var style1 = new ol.style.RegularShape({
radius: 5,
fill: new ol.style.Fill({
color: '#319FD3'
})
});
var style2 = new ol.style.RegularShape({
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.RegularShape({
radius: 5,
radius2: 3,
angle: 1.41,
points: 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.RegularShape({
radius: 5,
radius2: 3,
angle: 1.41,
points: 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.RegularShape({
radius: 5,
radius2: 3,
angle: 1.41,
points: 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.RegularShape({
radius: 5,
radius2: 3,
angle: 1.41,
points: 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.RegularShape({
radius: 5,
fill: new ol.style.Fill({
color: '#319FD3'
}),
stroke: new ol.style.Stroke({
color: '#319FD3'
})
});
var style2 = new ol.style.RegularShape({
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.RegularShape({
radius: 5,
fill: new ol.style.Fill({
color: '#319FD3'
}),
stroke: new ol.style.Stroke({
color: '#319FD3'
})
});
var style2 = new ol.style.RegularShape({
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.AtlasManager');
goog.require('ol.style.RegularShape');
goog.require('ol.style.Fill');
goog.require('ol.style.Stroke');

View File

@@ -0,0 +1,55 @@
goog.provide('ol.test.webgl.Buffer');
describe('ol.webgl.Buffer', function() {
describe('constructor', function() {
describe('without an argument', function() {
var b;
beforeEach(function() {
b = new ol.webgl.Buffer();
});
it('constructs an empty instance', function() {
expect(b.getArray()).to.be.empty();
});
});
describe('with a single array argument', function() {
var b;
beforeEach(function() {
b = new ol.webgl.Buffer([0, 1, 2, 3]);
});
it('constructs a populated instance', function() {
expect(b.getArray()).to.eql([0, 1, 2, 3]);
});
});
});
describe('with an empty instance', function() {
var b;
beforeEach(function() {
b = new ol.webgl.Buffer();
});
describe('getArray', function() {
it('returns an empty array', function() {
expect(b.getArray()).to.be.empty();
});
});
});
});
goog.require('ol.webgl.Buffer');