diff --git a/examples/layer-group.html b/examples/layer-group.html
new file mode 100644
index 0000000000..747d0b4c44
--- /dev/null
+++ b/examples/layer-group.html
@@ -0,0 +1,131 @@
+
+
+
+
+
+
+
+
+
+
Layer group example
+
Example of a map with layer group.
+
+
tilejson, input, bind, group, layergroup
+
+
+
+
Click on layer nodes below to change their properties.
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/layer-group.js b/examples/layer-group.js
new file mode 100644
index 0000000000..422d55d579
--- /dev/null
+++ b/examples/layer-group.js
@@ -0,0 +1,63 @@
+goog.require('ol.Map');
+goog.require('ol.RendererHints');
+goog.require('ol.View2D');
+goog.require('ol.dom.Input');
+goog.require('ol.layer.LayerGroup');
+goog.require('ol.layer.TileLayer');
+goog.require('ol.proj');
+goog.require('ol.source.MapQuestOpenAerial');
+goog.require('ol.source.TileJSON');
+
+var map = new ol.Map({
+ layers: [
+ new ol.layer.TileLayer({
+ source: new ol.source.MapQuestOpenAerial()
+ }), new ol.layer.LayerGroup({
+ layers: [
+ new ol.layer.TileLayer({
+ source: new ol.source.TileJSON({
+ url: 'http://api.tiles.mapbox.com/v3/' +
+ 'mapbox.20110804-hoa-foodinsecurity-3month.jsonp',
+ crossOrigin: 'anonymous'
+ })
+ }),
+ new ol.layer.TileLayer({
+ source: new ol.source.TileJSON({
+ url: 'http://api.tiles.mapbox.com/v3/' +
+ 'mapbox.world-borders-light.jsonp',
+ crossOrigin: 'anonymous'
+ })
+ })
+ ]
+ })
+ ],
+ renderers: ol.RendererHints.createFromQueryData(),
+ target: 'map',
+ view: new ol.View2D({
+ center: ol.proj.transform([37.40570, 8.81566], 'EPSG:4326', 'EPSG:3857'),
+ zoom: 4
+ })
+});
+
+function bindInputs(layerid, layer) {
+ new ol.dom.Input($(layerid + ' .visible')[0])
+ .bindTo('checked', layer, 'visible');
+ $.each(['opacity', 'hue', 'saturation', 'contrast', 'brightness'],
+ function(i, v) {
+ new ol.dom.Input($(layerid + ' .' + v)[0])
+ .bindTo('valueAsNumber', layer, v);
+ }
+ );
+}
+map.getLayers().forEach(function(layer, i) {
+ bindInputs('#layer' + i, layer);
+ if (layer instanceof ol.layer.LayerGroup) {
+ layer.getLayers().forEach(function(sublayer, j) {
+ bindInputs('#layer' + i + j, sublayer);
+ });
+ }
+});
+
+$('#layertree li > span').click(function() {
+ $(this).siblings('fieldset').toggle();
+}).siblings('fieldset').hide();
diff --git a/src/objectliterals.jsdoc b/src/objectliterals.jsdoc
index 5231e4d45d..d6cf8de273 100644
--- a/src/objectliterals.jsdoc
+++ b/src/objectliterals.jsdoc
@@ -46,7 +46,7 @@
* @property {ol.Collection|Array.
|undefined} controls
* Controls initially added to the map.
* @property {ol.Collection|undefined} interactions Interactions.
- * @property {Array.|ol.Collection|undefined} layers Layers.
+ * @property {Array.|ol.Collection|undefined} layers Layers.
* @property {ol.RendererHint|undefined} renderer Renderer.
* @property {Array.|undefined} renderers Renderers.
* @property {Element|string|undefined} target The container for the map.
@@ -305,6 +305,16 @@
* @property {number|undefined} threshold Minimal angle to start a rotation.
*/
+/**
+ * @typedef {Object} ol.layer.LayerBaseOptions
+ * @property {number|undefined} brightness Brightness.
+ * @property {number|undefined} contrast Contrast.
+ * @property {number|undefined} hue Hue.
+ * @property {number|undefined} opacity Opacity.
+ * @property {number|undefined} saturation Saturation.
+ * @property {boolean|undefined} visible Visibility.
+ */
+
/**
* @typedef {Object} ol.layer.LayerOptions
* @property {number|undefined} brightness Brightness.
@@ -316,6 +326,17 @@
* @property {boolean|undefined} visible Visibility. Default is true (visible).
*/
+/**
+ * @typedef {Object} ol.layer.LayerGroupOptions
+ * @property {number|undefined} brightness Brightness.
+ * @property {number|undefined} contrast Contrast.
+ * @property {number|undefined} hue Hue.
+ * @property {number|undefined} opacity Opacity.
+ * @property {number|undefined} saturation Saturation.
+ * @property {boolean|undefined} visible Visibility.
+ * @property {Array.|ol.Collection|undefined} layers Child layers.
+ */
+
/**
* @typedef {Object} ol.layer.TileLayerOptions
* @property {number|undefined} brightness Brightness.
diff --git a/src/ol/layer/layer.js b/src/ol/layer/layer.js
index 4f2d9aadf2..2e331ff189 100644
--- a/src/ol/layer/layer.js
+++ b/src/ol/layer/layer.js
@@ -1,49 +1,26 @@
goog.provide('ol.layer.Layer');
-goog.provide('ol.layer.LayerProperty');
-goog.provide('ol.layer.LayerState');
+goog.require('goog.asserts');
goog.require('goog.events');
goog.require('goog.events.EventType');
-goog.require('goog.math');
goog.require('goog.object');
-goog.require('ol.Object');
+goog.require('ol.layer.LayerBase');
goog.require('ol.source.Source');
-/**
- * @enum {string}
- */
-ol.layer.LayerProperty = {
- BRIGHTNESS: 'brightness',
- CONTRAST: 'contrast',
- HUE: 'hue',
- OPACITY: 'opacity',
- SATURATION: 'saturation',
- VISIBLE: 'visible'
-};
-
-
-/**
- * @typedef {{brightness: number,
- * contrast: number,
- * hue: number,
- * opacity: number,
- * ready: boolean,
- * saturation: number,
- * visible: boolean}}
- */
-ol.layer.LayerState;
-
-
/**
* @constructor
- * @extends {ol.Object}
+ * @extends {ol.layer.LayerBase}
* @param {ol.layer.LayerOptions} options Layer options.
*/
ol.layer.Layer = function(options) {
- goog.base(this);
+ var baseOptions = /** @type {ol.layer.LayerOptions} */
+ (goog.object.clone(options));
+ delete baseOptions.source;
+
+ goog.base(this, baseOptions);
/**
* @private
@@ -51,31 +28,13 @@ ol.layer.Layer = function(options) {
*/
this.source_ = options.source;
- var values = goog.object.clone(options);
- delete values.source;
-
- /** @type {number} */
- values.brightness = goog.isDef(values.brightness) ? values.brightness : 0;
- /** @type {number} */
- values.contrast = goog.isDef(values.contrast) ? values.contrast : 1;
- /** @type {number} */
- values.hue = goog.isDef(values.hue) ? values.hue : 0;
- /** @type {number} */
- values.opacity = goog.isDef(values.opacity) ? values.opacity : 1;
- /** @type {number} */
- values.saturation = goog.isDef(values.saturation) ? values.saturation : 1;
- /** @type {boolean} */
- values.visible = goog.isDef(values.visible) ? values.visible : true;
-
- this.setValues(values);
-
if (!this.source_.isReady()) {
goog.events.listenOnce(this.source_, goog.events.EventType.LOAD,
this.handleSourceLoad_, false, this);
}
};
-goog.inherits(ol.layer.Layer, ol.Object);
+goog.inherits(ol.layer.Layer, ol.layer.LayerBase);
/**
@@ -87,88 +46,30 @@ ol.layer.Layer.prototype.dispatchLoadEvent_ = function() {
/**
- * @return {number} Brightness.
+ * @inheritDoc
*/
-ol.layer.Layer.prototype.getBrightness = function() {
- return /** @type {number} */ (this.get(ol.layer.LayerProperty.BRIGHTNESS));
+ol.layer.Layer.prototype.getLayersArray = function(opt_array) {
+ var array = (goog.isDef(opt_array)) ? opt_array : [];
+ array.push(this);
+ return array;
};
-goog.exportProperty(
- ol.layer.Layer.prototype,
- 'getBrightness',
- ol.layer.Layer.prototype.getBrightness);
/**
- * @return {number} Contrast.
+ * @inheritDoc
*/
-ol.layer.Layer.prototype.getContrast = function() {
- return /** @type {number} */ (this.get(ol.layer.LayerProperty.CONTRAST));
-};
-goog.exportProperty(
- ol.layer.Layer.prototype,
- 'getContrast',
- ol.layer.Layer.prototype.getContrast);
-
-
-/**
- * @return {number} Hue.
- */
-ol.layer.Layer.prototype.getHue = function() {
- return /** @type {number} */ (this.get(ol.layer.LayerProperty.HUE));
-};
-goog.exportProperty(
- ol.layer.Layer.prototype,
- 'getHue',
- ol.layer.Layer.prototype.getHue);
-
-
-/**
- * @return {ol.layer.LayerState} Layer state.
- */
-ol.layer.Layer.prototype.getLayerState = function() {
- var brightness = this.getBrightness();
- var contrast = this.getContrast();
- var hue = this.getHue();
- var opacity = this.getOpacity();
- var ready = this.isReady();
- var saturation = this.getSaturation();
- var visible = this.getVisible();
- return {
- brightness: goog.isDef(brightness) ? goog.math.clamp(brightness, -1, 1) : 0,
- contrast: goog.isDef(contrast) ? Math.max(contrast, 0) : 1,
- hue: goog.isDef(hue) ? hue : 0,
- opacity: goog.isDef(opacity) ? goog.math.clamp(opacity, 0, 1) : 1,
- ready: ready,
- saturation: goog.isDef(saturation) ? Math.max(saturation, 0) : 1,
- visible: goog.isDef(visible) ? !!visible : true
+ol.layer.Layer.prototype.getLayerStatesArray = function(opt_obj) {
+ var obj = (goog.isDef(opt_obj)) ? opt_obj : {
+ layers: [],
+ layerStates: []
};
+ goog.asserts.assert(obj.layers.length === obj.layerStates.length);
+ obj.layers.push(this);
+ obj.layerStates.push(this.getLayerState());
+ return obj;
};
-/**
- * @return {number} Opacity.
- */
-ol.layer.Layer.prototype.getOpacity = function() {
- return /** @type {number} */ (this.get(ol.layer.LayerProperty.OPACITY));
-};
-goog.exportProperty(
- ol.layer.Layer.prototype,
- 'getOpacity',
- ol.layer.Layer.prototype.getOpacity);
-
-
-/**
- * @return {number} Saturation.
- */
-ol.layer.Layer.prototype.getSaturation = function() {
- return /** @type {number} */ (this.get(ol.layer.LayerProperty.SATURATION));
-};
-goog.exportProperty(
- ol.layer.Layer.prototype,
- 'getSaturation',
- ol.layer.Layer.prototype.getSaturation);
-
-
/**
* @return {ol.source.Source} Source.
*/
@@ -177,18 +78,6 @@ ol.layer.Layer.prototype.getSource = function() {
};
-/**
- * @return {boolean} Visible.
- */
-ol.layer.Layer.prototype.getVisible = function() {
- return /** @type {boolean} */ (this.get(ol.layer.LayerProperty.VISIBLE));
-};
-goog.exportProperty(
- ol.layer.Layer.prototype,
- 'getVisible',
- ol.layer.Layer.prototype.getVisible);
-
-
/**
* @private
*/
@@ -198,108 +87,8 @@ ol.layer.Layer.prototype.handleSourceLoad_ = function() {
/**
- * @return {boolean} Is ready.
+ * @inheritDoc
*/
ol.layer.Layer.prototype.isReady = function() {
return this.getSource().isReady();
};
-
-
-/**
- * Adjust the layer brightness. A value of -1 will render the layer completely
- * black. A value of 0 will leave the brightness unchanged. A value of 1 will
- * render the layer completely white. Other values are linear multipliers on
- * the effect (values are clamped between -1 and 1).
- *
- * The filter effects draft [1] says the brightness function is supposed to
- * render 0 black, 1 unchanged, and all other values as a linear multiplier.
- *
- * The current WebKit implementation clamps values between -1 (black) and 1
- * (white) [2]. There is a bug open to change the filter effect spec [3].
- *
- * TODO: revisit this if the spec is still unmodified before we release
- *
- * [1] https://dvcs.w3.org/hg/FXTF/raw-file/tip/filters/index.html
- * [2] https://github.com/WebKit/webkit/commit/8f4765e569
- * [3] https://www.w3.org/Bugs/Public/show_bug.cgi?id=15647
- *
- * @param {number} brightness Brightness.
- */
-ol.layer.Layer.prototype.setBrightness = function(brightness) {
- this.set(ol.layer.LayerProperty.BRIGHTNESS, brightness);
-};
-goog.exportProperty(
- ol.layer.Layer.prototype,
- 'setBrightness',
- ol.layer.Layer.prototype.setBrightness);
-
-
-/**
- * Adjust the layer contrast. A value of 0 will render the layer completely
- * grey. A value of 1 will leave the contrast unchanged. Other values are
- * linear multipliers on the effect (and values over 1 are permitted).
- *
- * @param {number} contrast Contrast.
- */
-ol.layer.Layer.prototype.setContrast = function(contrast) {
- this.set(ol.layer.LayerProperty.CONTRAST, contrast);
-};
-goog.exportProperty(
- ol.layer.Layer.prototype,
- 'setContrast',
- ol.layer.Layer.prototype.setContrast);
-
-
-/**
- * Apply a hue-rotation to the layer. A value of 0 will leave the hue
- * unchanged. Other values are radians around the color circle.
- * @param {number} hue Hue.
- */
-ol.layer.Layer.prototype.setHue = function(hue) {
- this.set(ol.layer.LayerProperty.HUE, hue);
-};
-goog.exportProperty(
- ol.layer.Layer.prototype,
- 'setHue',
- ol.layer.Layer.prototype.setHue);
-
-
-/**
- * @param {number} opacity Opacity.
- */
-ol.layer.Layer.prototype.setOpacity = function(opacity) {
- this.set(ol.layer.LayerProperty.OPACITY, opacity);
-};
-goog.exportProperty(
- ol.layer.Layer.prototype,
- 'setOpacity',
- ol.layer.Layer.prototype.setOpacity);
-
-
-/**
- * Adjust layer saturation. A value of 0 will render the layer completely
- * unsaturated. A value of 1 will leave the saturation unchanged. Other
- * values are linear multipliers of the effect (and values over 1 are
- * permitted).
- *
- * @param {number} saturation Saturation.
- */
-ol.layer.Layer.prototype.setSaturation = function(saturation) {
- this.set(ol.layer.LayerProperty.SATURATION, saturation);
-};
-goog.exportProperty(
- ol.layer.Layer.prototype,
- 'setSaturation',
- ol.layer.Layer.prototype.setSaturation);
-
-
-/**
- * @param {boolean} visible Visible.
- */
-ol.layer.Layer.prototype.setVisible = function(visible) {
- this.set(ol.layer.LayerProperty.VISIBLE, visible);
-};
-goog.exportProperty(
- ol.layer.Layer.prototype,
- 'setVisible',
- ol.layer.Layer.prototype.setVisible);
diff --git a/src/ol/layer/layerbase.js b/src/ol/layer/layerbase.js
new file mode 100644
index 0000000000..1b7d0abdcd
--- /dev/null
+++ b/src/ol/layer/layerbase.js
@@ -0,0 +1,285 @@
+goog.provide('ol.layer.LayerBase');
+goog.provide('ol.layer.LayerProperty');
+goog.provide('ol.layer.LayerState');
+
+goog.require('goog.math');
+goog.require('goog.object');
+goog.require('ol.Object');
+
+
+/**
+ * @enum {string}
+ */
+ol.layer.LayerProperty = {
+ BRIGHTNESS: 'brightness',
+ CONTRAST: 'contrast',
+ HUE: 'hue',
+ OPACITY: 'opacity',
+ SATURATION: 'saturation',
+ VISIBLE: 'visible'
+};
+
+
+/**
+ * @typedef {{brightness: number,
+ * contrast: number,
+ * hue: number,
+ * opacity: number,
+ * ready: boolean,
+ * saturation: number,
+ * visible: boolean}}
+ */
+ol.layer.LayerState;
+
+
+
+/**
+ * @constructor
+ * @extends {ol.Object}
+ * @param {ol.layer.LayerBaseOptions} options Layer options.
+ */
+ol.layer.LayerBase = function(options) {
+
+ goog.base(this);
+
+ var values = goog.object.clone(options);
+
+ /** @type {number} */
+ values.brightness = goog.isDef(values.brightness) ? values.brightness : 0;
+ /** @type {number} */
+ values.contrast = goog.isDef(values.contrast) ? values.contrast : 1;
+ /** @type {number} */
+ values.hue = goog.isDef(values.hue) ? values.hue : 0;
+ /** @type {number} */
+ values.opacity = goog.isDef(values.opacity) ? values.opacity : 1;
+ /** @type {number} */
+ values.saturation = goog.isDef(values.saturation) ? values.saturation : 1;
+ /** @type {boolean} */
+ values.visible = goog.isDef(values.visible) ? values.visible : true;
+
+ this.setValues(values);
+
+};
+goog.inherits(ol.layer.LayerBase, ol.Object);
+
+
+/**
+ * @return {number} Brightness.
+ */
+ol.layer.LayerBase.prototype.getBrightness = function() {
+ return /** @type {number} */ (this.get(ol.layer.LayerProperty.BRIGHTNESS));
+};
+goog.exportProperty(
+ ol.layer.LayerBase.prototype,
+ 'getBrightness',
+ ol.layer.LayerBase.prototype.getBrightness);
+
+
+/**
+ * @return {number} Contrast.
+ */
+ol.layer.LayerBase.prototype.getContrast = function() {
+ return /** @type {number} */ (this.get(ol.layer.LayerProperty.CONTRAST));
+};
+goog.exportProperty(
+ ol.layer.LayerBase.prototype,
+ 'getContrast',
+ ol.layer.LayerBase.prototype.getContrast);
+
+
+/**
+ * @return {number} Hue.
+ */
+ol.layer.LayerBase.prototype.getHue = function() {
+ return /** @type {number} */ (this.get(ol.layer.LayerProperty.HUE));
+};
+goog.exportProperty(
+ ol.layer.LayerBase.prototype,
+ 'getHue',
+ ol.layer.LayerBase.prototype.getHue);
+
+
+/**
+ * @return {ol.layer.LayerState} Layer state.
+ */
+ol.layer.LayerBase.prototype.getLayerState = function() {
+ var brightness = this.getBrightness();
+ var contrast = this.getContrast();
+ var hue = this.getHue();
+ var opacity = this.getOpacity();
+ var ready = this.isReady();
+ var saturation = this.getSaturation();
+ var visible = this.getVisible();
+ return {
+ brightness: goog.isDef(brightness) ? goog.math.clamp(brightness, -1, 1) : 0,
+ contrast: goog.isDef(contrast) ? Math.max(contrast, 0) : 1,
+ hue: goog.isDef(hue) ? hue : 0,
+ opacity: goog.isDef(opacity) ? goog.math.clamp(opacity, 0, 1) : 1,
+ ready: ready,
+ saturation: goog.isDef(saturation) ? Math.max(saturation, 0) : 1,
+ visible: goog.isDef(visible) ? !!visible : true
+ };
+};
+
+
+/**
+ * @param {Array.=} opt_array Array of layers (to be
+ * modified in place).
+ * @return {Array.} Array of layers.
+ */
+ol.layer.LayerBase.prototype.getLayersArray = goog.abstractMethod;
+
+
+/**
+ * @param {{
+ * layers: Array.,
+ * layerStates: Array.}=} opt_obj Object that store
+ * both the layers and the layerStates (to be modified in place).
+ * @return {{
+ * layers: Array.,
+ * layerStates: Array.}} Object that store both the
+ * layers and the layerStates.
+ */
+ol.layer.LayerBase.prototype.getLayerStatesArray = goog.abstractMethod;
+
+
+/**
+ * @return {number} Opacity.
+ */
+ol.layer.LayerBase.prototype.getOpacity = function() {
+ return /** @type {number} */ (this.get(ol.layer.LayerProperty.OPACITY));
+};
+goog.exportProperty(
+ ol.layer.LayerBase.prototype,
+ 'getOpacity',
+ ol.layer.LayerBase.prototype.getOpacity);
+
+
+/**
+ * @return {number} Saturation.
+ */
+ol.layer.LayerBase.prototype.getSaturation = function() {
+ return /** @type {number} */ (this.get(ol.layer.LayerProperty.SATURATION));
+};
+goog.exportProperty(
+ ol.layer.LayerBase.prototype,
+ 'getSaturation',
+ ol.layer.LayerBase.prototype.getSaturation);
+
+
+/**
+ * @return {boolean} Visible.
+ */
+ol.layer.LayerBase.prototype.getVisible = function() {
+ return /** @type {boolean} */ (this.get(ol.layer.LayerProperty.VISIBLE));
+};
+goog.exportProperty(
+ ol.layer.LayerBase.prototype,
+ 'getVisible',
+ ol.layer.LayerBase.prototype.getVisible);
+
+
+/**
+ * @return {boolean} Is ready.
+ */
+ol.layer.LayerBase.prototype.isReady = goog.abstractMethod;
+
+
+/**
+ * Adjust the layer brightness. A value of -1 will render the layer completely
+ * black. A value of 0 will leave the brightness unchanged. A value of 1 will
+ * render the layer completely white. Other values are linear multipliers on
+ * the effect (values are clamped between -1 and 1).
+ *
+ * The filter effects draft [1] says the brightness function is supposed to
+ * render 0 black, 1 unchanged, and all other values as a linear multiplier.
+ *
+ * The current WebKit implementation clamps values between -1 (black) and 1
+ * (white) [2]. There is a bug open to change the filter effect spec [3].
+ *
+ * TODO: revisit this if the spec is still unmodified before we release
+ *
+ * [1] https://dvcs.w3.org/hg/FXTF/raw-file/tip/filters/index.html
+ * [2] https://github.com/WebKit/webkit/commit/8f4765e569
+ * [3] https://www.w3.org/Bugs/Public/show_bug.cgi?id=15647
+ *
+ * @param {number} brightness Brightness.
+ */
+ol.layer.LayerBase.prototype.setBrightness = function(brightness) {
+ this.set(ol.layer.LayerProperty.BRIGHTNESS, brightness);
+};
+goog.exportProperty(
+ ol.layer.LayerBase.prototype,
+ 'setBrightness',
+ ol.layer.LayerBase.prototype.setBrightness);
+
+
+/**
+ * Adjust the layer contrast. A value of 0 will render the layer completely
+ * grey. A value of 1 will leave the contrast unchanged. Other values are
+ * linear multipliers on the effect (and values over 1 are permitted).
+ *
+ * @param {number} contrast Contrast.
+ */
+ol.layer.LayerBase.prototype.setContrast = function(contrast) {
+ this.set(ol.layer.LayerProperty.CONTRAST, contrast);
+};
+goog.exportProperty(
+ ol.layer.LayerBase.prototype,
+ 'setContrast',
+ ol.layer.LayerBase.prototype.setContrast);
+
+
+/**
+ * Apply a hue-rotation to the layer. A value of 0 will leave the hue
+ * unchanged. Other values are radians around the color circle.
+ * @param {number} hue Hue.
+ */
+ol.layer.LayerBase.prototype.setHue = function(hue) {
+ this.set(ol.layer.LayerProperty.HUE, hue);
+};
+goog.exportProperty(
+ ol.layer.LayerBase.prototype,
+ 'setHue',
+ ol.layer.LayerBase.prototype.setHue);
+
+
+/**
+ * @param {number} opacity Opacity.
+ */
+ol.layer.LayerBase.prototype.setOpacity = function(opacity) {
+ this.set(ol.layer.LayerProperty.OPACITY, opacity);
+};
+goog.exportProperty(
+ ol.layer.LayerBase.prototype,
+ 'setOpacity',
+ ol.layer.LayerBase.prototype.setOpacity);
+
+
+/**
+ * Adjust layer saturation. A value of 0 will render the layer completely
+ * unsaturated. A value of 1 will leave the saturation unchanged. Other
+ * values are linear multipliers of the effect (and values over 1 are
+ * permitted).
+ *
+ * @param {number} saturation Saturation.
+ */
+ol.layer.LayerBase.prototype.setSaturation = function(saturation) {
+ this.set(ol.layer.LayerProperty.SATURATION, saturation);
+};
+goog.exportProperty(
+ ol.layer.LayerBase.prototype,
+ 'setSaturation',
+ ol.layer.LayerBase.prototype.setSaturation);
+
+
+/**
+ * @param {boolean} visible Visible.
+ */
+ol.layer.LayerBase.prototype.setVisible = function(visible) {
+ this.set(ol.layer.LayerProperty.VISIBLE, visible);
+};
+goog.exportProperty(
+ ol.layer.LayerBase.prototype,
+ 'setVisible',
+ ol.layer.LayerBase.prototype.setVisible);
diff --git a/src/ol/layer/layergroup.exports b/src/ol/layer/layergroup.exports
new file mode 100644
index 0000000000..9a4aeb979b
--- /dev/null
+++ b/src/ol/layer/layergroup.exports
@@ -0,0 +1 @@
+@exportClass ol.layer.LayerGroup ol.layer.LayerGroupOptions
diff --git a/src/ol/layer/layergroup.js b/src/ol/layer/layergroup.js
new file mode 100644
index 0000000000..85dd6c85bd
--- /dev/null
+++ b/src/ol/layer/layergroup.js
@@ -0,0 +1,222 @@
+goog.provide('ol.layer.LayerGroup');
+
+goog.require('goog.array');
+goog.require('goog.asserts');
+goog.require('goog.events');
+goog.require('goog.events.EventType');
+goog.require('goog.math');
+goog.require('goog.object');
+goog.require('ol.Collection');
+goog.require('ol.CollectionEvent');
+goog.require('ol.CollectionEventType');
+goog.require('ol.Object');
+goog.require('ol.layer.Layer');
+goog.require('ol.layer.LayerBase');
+
+
+/**
+ * @enum {string}
+ */
+ol.layer.LayerGroupProperty = {
+ LAYERS: 'layers'
+};
+
+
+
+/**
+ * @constructor
+ * @extends {ol.layer.LayerBase}
+ * @param {ol.layer.LayerGroupOptions=} opt_options Layer options.
+ */
+ol.layer.LayerGroup = function(opt_options) {
+
+ var options = goog.isDef(opt_options) ? opt_options : {};
+ var baseOptions = /** @type {ol.layer.LayerGroupOptions} */
+ (goog.object.clone(options));
+ delete baseOptions.layers;
+
+ var layers = options.layers;
+
+ goog.base(this, baseOptions);
+
+ /**
+ * @private
+ * @type {Object.}
+ */
+ this.listenerKeys_ = null;
+
+ goog.events.listen(this,
+ ol.Object.getChangeEventType(ol.layer.LayerGroupProperty.LAYERS),
+ this.handleLayersChanged_, false, this);
+
+ if (goog.isDef(layers)) {
+ if (goog.isArray(layers)) {
+ layers = new ol.Collection(goog.array.clone(layers));
+ } else {
+ goog.asserts.assertInstanceof(layers, ol.Collection);
+ layers = layers;
+ }
+ } else {
+ layers = new ol.Collection();
+ }
+
+ this.setLayers(layers);
+
+};
+goog.inherits(ol.layer.LayerGroup, ol.layer.LayerBase);
+
+
+/**
+ * @param {goog.events.Event} event Event.
+ * @private
+ */
+ol.layer.LayerGroup.prototype.handleLayersChanged_ = function(event) {
+ if (!goog.isNull(this.listenerKeys_)) {
+ goog.array.forEach(
+ goog.object.getValues(this.listenerKeys_), goog.events.unlistenByKey);
+ this.listenerKeys_ = null;
+ }
+
+ var layers = this.getLayers();
+ if (goog.isDefAndNotNull(layers)) {
+ this.listenerKeys_ = {
+ 'add': goog.events.listen(layers, ol.CollectionEventType.ADD,
+ this.handleLayersAdd_, false, this),
+ 'remove': goog.events.listen(layers, ol.CollectionEventType.REMOVE,
+ this.handleLayersRemove_, false, this)
+ };
+
+ var layersArray = layers.getArray();
+ var i, ii, layer;
+ for (i = 0, ii = layersArray.length; i < ii; i++) {
+ layer = layersArray[i];
+ this.listenerKeys_[goog.getUid(layer).toString()] =
+ goog.events.listen(layer, goog.events.EventType.CHANGE,
+ this.handleLayerChange_, false, this);
+ }
+ }
+
+ this.dispatchChangeEvent_();
+};
+
+
+/**
+ * @param {ol.CollectionEvent} collectionEvent Collection event.
+ * @private
+ */
+ol.layer.LayerGroup.prototype.handleLayersAdd_ = function(collectionEvent) {
+ var layer = /** @type {ol.layer.LayerBase} */ (collectionEvent.elem);
+ this.listenerKeys_[goog.getUid(layer).toString()] = goog.events.listen(
+ layer, goog.events.EventType.CHANGE, this.handleLayerChange_, false,
+ this);
+ this.dispatchChangeEvent_();
+};
+
+
+/**
+ * @param {ol.CollectionEvent} collectionEvent Collection event.
+ * @private
+ */
+ol.layer.LayerGroup.prototype.handleLayersRemove_ = function(collectionEvent) {
+ var layer = /** @type {ol.layer.LayerBase} */ (collectionEvent.elem);
+ var key = goog.getUid(layer).toString();
+ goog.events.unlistenByKey(this.listenerKeys_[key]);
+ delete this.listenerKeys_[key];
+ this.dispatchChangeEvent_();
+};
+
+
+/**
+ * @private
+ */
+ol.layer.LayerGroup.prototype.handleLayerChange_ = function() {
+ this.dispatchChangeEvent_();
+};
+
+
+/**
+ * @private
+ */
+ol.layer.LayerGroup.prototype.dispatchChangeEvent_ = function() {
+ this.dispatchEvent(goog.events.EventType.CHANGE);
+};
+
+
+/**
+ * @return {ol.Collection} Collection of layers.
+ */
+ol.layer.LayerGroup.prototype.getLayers = function() {
+ return /** @type {ol.Collection} */ (this.get(
+ ol.layer.LayerGroupProperty.LAYERS));
+};
+goog.exportProperty(
+ ol.layer.LayerGroup.prototype,
+ 'getLayers',
+ ol.layer.LayerGroup.prototype.getLayers);
+
+
+/**
+ * @param {ol.Collection} layers Collection of layers.
+ */
+ol.layer.LayerGroup.prototype.setLayers = function(layers) {
+ this.set(ol.layer.LayerGroupProperty.LAYERS, layers);
+};
+goog.exportProperty(
+ ol.layer.LayerGroup.prototype,
+ 'setLayers',
+ ol.layer.LayerGroup.prototype.setLayers);
+
+
+/**
+ * @inheritDoc
+ */
+ol.layer.LayerGroup.prototype.getLayersArray = function(opt_array) {
+ var array = (goog.isDef(opt_array)) ? opt_array : [];
+ this.getLayers().forEach(function(layer) {
+ layer.getLayersArray(array);
+ });
+ return array;
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.layer.LayerGroup.prototype.getLayerStatesArray = function(opt_obj) {
+ var obj = (goog.isDef(opt_obj)) ? opt_obj : {
+ layers: [],
+ layerStates: []
+ };
+ goog.asserts.assert(obj.layers.length === obj.layerStates.length);
+ var pos = obj.layers.length;
+
+ this.getLayers().forEach(function(layer) {
+ layer.getLayerStatesArray(obj);
+ });
+
+ var ownLayerState = this.getLayerState();
+ var i, ii, layerState;
+ for (i = pos, ii = obj.layerStates.length; i < ii; i++) {
+ layerState = obj.layerStates[i];
+ layerState.brightness = goog.math.clamp(
+ layerState.brightness + ownLayerState.brightness, -1, 1);
+ layerState.contrast *= ownLayerState.contrast;
+ layerState.hue += ownLayerState.hue;
+ layerState.opacity *= ownLayerState.opacity;
+ layerState.saturation *= ownLayerState.saturation;
+ layerState.visible = layerState.visible && ownLayerState.visible;
+ }
+
+ return obj;
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.layer.LayerGroup.prototype.isReady = function() {
+ return null === goog.array.find(
+ this.getLayers().getArray(), function(elt, index, array) {
+ return !elt.isReady();
+ });
+};
diff --git a/src/ol/layer/vectorlayer.js b/src/ol/layer/vectorlayer.js
index 42b53fd9ca..e024064f7f 100644
--- a/src/ol/layer/vectorlayer.js
+++ b/src/ol/layer/vectorlayer.js
@@ -1,8 +1,8 @@
goog.provide('ol.layer.Vector');
+goog.provide('ol.layer.VectorLayerEventType');
goog.require('goog.array');
goog.require('goog.asserts');
-goog.require('goog.events.EventType');
goog.require('goog.object');
goog.require('ol.Feature');
goog.require('ol.expr');
@@ -221,10 +221,18 @@ ol.layer.FeatureCache.prototype.remove = function(feature) {
/**
- * TODO: Create a VectorLayerEvent with ADD and REMOVE event types
+ * @enum {string}
+ */
+ol.layer.VectorLayerEventType = {
+ ADD: 'add',
+ REMOVE: 'remove'
+};
+
+
+/**
* @typedef {{extent: (ol.Extent|undefined),
* features: (Array.|undefined),
- * type: goog.events.EventType}}
+ * type: ol.layer.VectorLayerEventType}}
*/
ol.layer.VectorLayerEventObject;
@@ -304,7 +312,7 @@ ol.layer.Vector.prototype.addFeatures = function(features) {
this.dispatchEvent(/** @type {ol.layer.VectorLayerEventObject} */ ({
extent: extent,
features: features,
- type: goog.events.EventType.CHANGE
+ type: ol.layer.VectorLayerEventType.ADD
}));
};
@@ -517,7 +525,7 @@ ol.layer.Vector.prototype.removeFeatures = function(features) {
this.dispatchEvent(/** @type {ol.layer.VectorLayerEventObject} */ ({
extent: extent,
features: features,
- type: goog.events.EventType.CHANGE
+ type: ol.layer.VectorLayerEventType.REMOVE
}));
};
diff --git a/src/ol/map.exports b/src/ol/map.exports
index 71fe519081..f676fa46de 100644
--- a/src/ol/map.exports
+++ b/src/ol/map.exports
@@ -6,6 +6,7 @@
@exportProperty ol.Map.prototype.getFeatureInfo
@exportProperty ol.Map.prototype.getFeatures
@exportProperty ol.Map.prototype.getInteractions
+@exportProperty ol.Map.prototype.getLayers
@exportProperty ol.Map.prototype.getRenderer
@exportProperty ol.Map.prototype.removeControl
@exportProperty ol.Map.prototype.removeLayer
diff --git a/src/ol/map.js b/src/ol/map.js
index 3dded4b5d5..8fa48bedfe 100644
--- a/src/ol/map.js
+++ b/src/ol/map.js
@@ -32,8 +32,6 @@ goog.require('goog.style');
goog.require('goog.vec.Mat4');
goog.require('ol.BrowserFeature');
goog.require('ol.Collection');
-goog.require('ol.CollectionEvent');
-goog.require('ol.CollectionEventType');
goog.require('ol.FrameState');
goog.require('ol.IView');
goog.require('ol.MapBrowserEvent');
@@ -55,7 +53,8 @@ goog.require('ol.ViewHint');
goog.require('ol.control.defaults');
goog.require('ol.extent');
goog.require('ol.interaction.defaults');
-goog.require('ol.layer.Layer');
+goog.require('ol.layer.LayerBase');
+goog.require('ol.layer.LayerGroup');
goog.require('ol.proj');
goog.require('ol.proj.addCommonProjections');
goog.require('ol.renderer.Map');
@@ -111,7 +110,7 @@ ol.DEFAULT_RENDERER_HINTS = [
* @enum {string}
*/
ol.MapProperty = {
- LAYERS: 'layers',
+ LAYERGROUP: 'layergroup',
SIZE: 'size',
TARGET: 'target',
VIEW: 'view'
@@ -200,6 +199,12 @@ ol.Map = function(options) {
*/
this.viewPropertyListenerKey_ = null;
+ /**
+ * @private
+ * @type {goog.events.Key}
+ */
+ this.layerGroupPropertyListenerKey_ = null;
+
/**
* @private
* @type {Element}
@@ -307,14 +312,9 @@ ol.Map = function(options) {
goog.bind(this.getTilePriority, this),
goog.bind(this.handleTileChange_, this));
- /**
- * @private
- * @type {Array.}
- */
- this.layersListenerKeys_ = null;
-
- goog.events.listen(this, ol.Object.getChangeEventType(ol.MapProperty.LAYERS),
- this.handleLayersChanged_, false, this);
+ goog.events.listen(
+ this, ol.Object.getChangeEventType(ol.MapProperty.LAYERGROUP),
+ this.handleLayerGroupChanged_, false, this);
goog.events.listen(this, ol.Object.getChangeEventType(ol.MapProperty.VIEW),
this.handleViewChanged_, false, this);
goog.events.listen(this, ol.Object.getChangeEventType(ol.MapProperty.SIZE),
@@ -352,10 +352,10 @@ ol.Map.prototype.addControl = function(control) {
/**
* Adds the given layer to the top of this map.
- * @param {ol.layer.Layer} layer Layer.
+ * @param {ol.layer.LayerBase} layer Layer.
*/
ol.Map.prototype.addLayer = function(layer) {
- var layers = this.getLayers();
+ var layers = this.getLayerGroup().getLayers();
goog.asserts.assert(goog.isDef(layers));
layers.push(layer);
};
@@ -465,7 +465,7 @@ ol.Map.prototype.getControls = function() {
*/
ol.Map.prototype.getFeatureInfo = function(options) {
var layers = goog.isDefAndNotNull(options.layers) ?
- options.layers : this.getLayers().getArray();
+ options.layers : this.getLayerGroup().getLayersArray();
this.getRenderer().getFeatureInfoForPixel(
options.pixel, layers, options.success, options.error);
};
@@ -478,7 +478,7 @@ ol.Map.prototype.getFeatureInfo = function(options) {
*/
ol.Map.prototype.getFeatures = function(options) {
var layers = goog.isDefAndNotNull(options.layers) ?
- options.layers : this.getLayers().getArray();
+ options.layers : this.getLayerGroup().getLayersArray();
this.getRenderer().getFeaturesForPixel(
options.pixel, layers, options.success, options.error);
};
@@ -494,17 +494,27 @@ ol.Map.prototype.getInteractions = function() {
};
+/**
+ * Get the layergroup associated with this map.
+ * @return {ol.layer.LayerGroup} LayerGroup.
+ */
+ol.Map.prototype.getLayerGroup = function() {
+ return /** @type {ol.layer.LayerGroup} */ (
+ this.get(ol.MapProperty.LAYERGROUP));
+};
+goog.exportProperty(
+ ol.Map.prototype,
+ 'getLayerGroup',
+ ol.Map.prototype.getLayerGroup);
+
+
/**
* Get the collection of layers associated with this map.
* @return {ol.Collection} Layers.
*/
ol.Map.prototype.getLayers = function() {
- return /** @type {ol.Collection} */ (this.get(ol.MapProperty.LAYERS));
+ return this.getLayerGroup().getLayers();
};
-goog.exportProperty(
- ol.Map.prototype,
- 'getLayers',
- ol.Map.prototype.getLayers);
/**
@@ -615,46 +625,6 @@ ol.Map.prototype.handleBrowserEvent = function(browserEvent, opt_type) {
};
-/**
- * @param {ol.CollectionEvent} collectionEvent Collection event.
- * @private
- */
-ol.Map.prototype.handleLayersAdd_ = function(collectionEvent) {
- this.render();
-};
-
-
-/**
- * @param {goog.events.Event} event Event.
- * @private
- */
-ol.Map.prototype.handleLayersChanged_ = function(event) {
- if (!goog.isNull(this.layersListenerKeys_)) {
- goog.array.forEach(this.layersListenerKeys_, goog.events.unlistenByKey);
- this.layersListenerKeys_ = null;
- }
- var layers = this.getLayers();
- if (goog.isDefAndNotNull(layers)) {
- this.layersListenerKeys_ = [
- goog.events.listen(layers, ol.CollectionEventType.ADD,
- this.handleLayersAdd_, false, this),
- goog.events.listen(layers, ol.CollectionEventType.REMOVE,
- this.handleLayersRemove_, false, this)
- ];
- }
- this.render();
-};
-
-
-/**
- * @param {ol.CollectionEvent} collectionEvent Collection event.
- * @private
- */
-ol.Map.prototype.handleLayersRemove_ = function(collectionEvent) {
- this.render();
-};
-
-
/**
* @param {ol.MapBrowserEvent} mapBrowserEvent The event to handle.
*/
@@ -788,6 +758,33 @@ ol.Map.prototype.handleViewChanged_ = function() {
};
+/**
+ * @param {goog.events.Event} event Event.
+ * @private
+ */
+ol.Map.prototype.handleLayerGroupPropertyChanged_ = function(event) {
+ this.render();
+};
+
+
+/**
+ * @private
+ */
+ol.Map.prototype.handleLayerGroupChanged_ = function() {
+ if (!goog.isNull(this.layerGroupPropertyListenerKey_)) {
+ goog.events.unlistenByKey(this.layerGroupPropertyListenerKey_);
+ this.layerGroupPropertyListenerKey_ = null;
+ }
+ var layerGroup = this.getLayerGroup();
+ if (goog.isDefAndNotNull(layerGroup)) {
+ this.layerGroupPropertyListenerKey_ = goog.events.listen(
+ layerGroup, ol.ObjectEventType.CHANGE,
+ this.handleLayerGroupPropertyChanged_, false, this);
+ }
+ this.render();
+};
+
+
/**
* @return {boolean} Is defined.
*/
@@ -845,14 +842,14 @@ ol.Map.prototype.removeControl = function(control) {
/**
* Removes the given layer from the map.
- * @param {ol.layer.Layer} layer Layer.
- * @return {ol.layer.Layer|undefined} The removed layer or undefined if the
+ * @param {ol.layer.LayerBase} layer Layer.
+ * @return {ol.layer.LayerBase|undefined} The removed layer or undefined if the
* layer was not found.
*/
ol.Map.prototype.removeLayer = function(layer) {
- var layers = this.getLayers();
+ var layers = this.getLayerGroup().getLayers();
goog.asserts.assert(goog.isDef(layers));
- return /** @type {ol.layer.Layer|undefined} */ (layers.remove(layer));
+ return /** @type {ol.layer.LayerBase|undefined} */ (layers.remove(layer));
};
@@ -869,21 +866,20 @@ ol.Map.prototype.renderFrame_ = function(time) {
}
var size = this.getSize();
- var layers = this.getLayers();
- var layersArray = goog.isDef(layers) ?
- /** @type {Array.} */ (layers.getArray()) : undefined;
var view = this.getView();
var view2D = goog.isDef(view) ? this.getView().getView2D() : undefined;
/** @type {?ol.FrameState} */
var frameState = null;
- if (goog.isDef(layersArray) && goog.isDef(size) && goog.isDef(view2D) &&
- view2D.isDef()) {
+ if (goog.isDef(size) && goog.isDef(view2D) && view2D.isDef()) {
var viewHints = view.getHints();
+ var obj = this.getLayerGroup().getLayerStatesArray();
+ var layersArray = obj.layers;
+ var layerStatesArray = obj.layerStates;
var layerStates = {};
var layer;
for (i = 0, ii = layersArray.length; i < ii; ++i) {
layer = layersArray[i];
- layerStates[goog.getUid(layer)] = layer.getLayerState();
+ layerStates[goog.getUid(layer)] = layerStatesArray[i];
}
view2DState = view2D.getView2DState();
frameState = {
@@ -947,16 +943,16 @@ ol.Map.prototype.renderFrame_ = function(time) {
/**
- * Sets the whole collection of layers for this map.
- * @param {ol.Collection} layers Layers.
+ * Sets the layergroup of this map.
+ * @param {ol.layer.LayerGroup} layerGroup Layergroup.
*/
-ol.Map.prototype.setLayers = function(layers) {
- this.set(ol.MapProperty.LAYERS, layers);
+ol.Map.prototype.setLayerGroup = function(layerGroup) {
+ this.set(ol.MapProperty.LAYERGROUP, layerGroup);
};
goog.exportProperty(
ol.Map.prototype,
- 'setLayers',
- ol.Map.prototype.setLayers);
+ 'setLayerGroup',
+ ol.Map.prototype.setLayerGroup);
/**
@@ -1063,18 +1059,9 @@ ol.Map.createOptionsInternal = function(options) {
*/
var values = {};
- var layers;
- if (goog.isDef(options.layers)) {
- if (goog.isArray(options.layers)) {
- layers = new ol.Collection(goog.array.clone(options.layers));
- } else {
- goog.asserts.assertInstanceof(options.layers, ol.Collection);
- layers = options.layers;
- }
- } else {
- layers = new ol.Collection();
- }
- values[ol.MapProperty.LAYERS] = layers;
+ var layerGroup = (options.layers instanceof ol.layer.LayerGroup) ?
+ options.layers : new ol.layer.LayerGroup({layers: options.layers});
+ values[ol.MapProperty.LAYERGROUP] = layerGroup;
values[ol.MapProperty.TARGET] = options.target;
diff --git a/src/ol/renderer/canvas/canvasvectorlayerrenderer.js b/src/ol/renderer/canvas/canvasvectorlayerrenderer.js
index 94260aac9f..51bb84eb39 100644
--- a/src/ol/renderer/canvas/canvasvectorlayerrenderer.js
+++ b/src/ol/renderer/canvas/canvasvectorlayerrenderer.js
@@ -3,7 +3,6 @@ goog.provide('ol.renderer.canvas.VectorLayer');
goog.require('goog.dom');
goog.require('goog.dom.TagName');
goog.require('goog.events');
-goog.require('goog.events.EventType');
goog.require('goog.object');
goog.require('goog.vec.Mat4');
goog.require('ol.Pixel');
@@ -14,6 +13,7 @@ goog.require('ol.ViewHint');
goog.require('ol.extent');
goog.require('ol.geom.GeometryType');
goog.require('ol.layer.Vector');
+goog.require('ol.layer.VectorLayerEventType');
goog.require('ol.renderer.canvas.Layer');
goog.require('ol.renderer.canvas.VectorRenderer');
goog.require('ol.tilegrid.TileGrid');
@@ -86,8 +86,11 @@ ol.renderer.canvas.VectorLayer = function(mapRenderer, layer) {
*/
this.tileCache_ = new ol.TileCache(
ol.renderer.canvas.VectorLayer.TILECACHE_SIZE);
- goog.events.listen(layer, goog.events.EventType.CHANGE,
- this.handleLayerChange_, false, this);
+ goog.events.listen(layer, [
+ ol.layer.VectorLayerEventType.ADD,
+ ol.layer.VectorLayerEventType.REMOVE
+ ],
+ this.handleLayerChange_, false, this);
/**
* @private
diff --git a/src/ol/renderer/webgl/webglmaprenderer.js b/src/ol/renderer/webgl/webglmaprenderer.js
index 54cfe1a895..d3d117e174 100644
--- a/src/ol/renderer/webgl/webglmaprenderer.js
+++ b/src/ol/renderer/webgl/webglmaprenderer.js
@@ -645,7 +645,7 @@ ol.renderer.webgl.Map.prototype.renderFrame = function(frameState) {
gl.uniformMatrix4fv(locations.u_colorMatrix, false,
layerRenderer.getColorMatrix());
}
- gl.uniform1f(locations.u_opacity, layer.getOpacity());
+ gl.uniform1f(locations.u_opacity, layerState.opacity);
gl.bindTexture(goog.webgl.TEXTURE_2D, layerRenderer.getTexture());
gl.drawArrays(goog.webgl.TRIANGLE_STRIP, 0, 4);
diff --git a/test/spec/ol/layer/layer.test.js b/test/spec/ol/layer/layer.test.js
index 218e88b7c3..fa1fdf9f9e 100644
--- a/test/spec/ol/layer/layer.test.js
+++ b/test/spec/ol/layer/layer.test.js
@@ -46,6 +46,18 @@ describe('ol.layer.Layer', function() {
expect(layer.getVisible()).to.be(true);
});
+ it('provides default layerState', function() {
+ expect(layer.getLayerState()).to.eql({
+ brightness: 0,
+ contrast: 1,
+ hue: 0,
+ opacity: 1,
+ saturation: 1,
+ visible: true,
+ ready: true
+ });
+ });
+
});
describe('constructor (options)', function() {
@@ -71,12 +83,91 @@ describe('ol.layer.Layer', function() {
expect(layer.getSaturation()).to.be(5);
expect(layer.getVisible()).to.be(false);
expect(layer.get('foo')).to.be(42);
+ expect(layer.getLayerState()).to.eql({
+ brightness: 0.5,
+ contrast: 10,
+ hue: 180,
+ opacity: 0.5,
+ saturation: 5,
+ visible: false,
+ ready: true
+ });
goog.dispose(layer);
});
});
+ describe('#getLayerState', function() {
+
+ var layer;
+
+ beforeEach(function() {
+ layer = new ol.layer.Layer({
+ source: new ol.source.Source({
+ projection: ol.proj.get('EPSG:4326')
+ })
+ });
+ });
+
+ afterEach(function() {
+ goog.dispose(layer);
+ });
+
+ it('returns a layerState from the properties values', function() {
+ layer.setBrightness(-0.7);
+ layer.setContrast(0.3);
+ layer.setHue(-0.3);
+ layer.setOpacity(0.3);
+ layer.setSaturation(0.3);
+ layer.setVisible(false);
+ expect(layer.getLayerState()).to.eql({
+ brightness: -0.7,
+ contrast: 0.3,
+ hue: -0.3,
+ opacity: 0.3,
+ saturation: 0.3,
+ visible: false,
+ ready: true
+ });
+ });
+
+ it('returns a layerState with clamped values', function() {
+ layer.setBrightness(1.5);
+ layer.setContrast(-0.7);
+ layer.setHue(42);
+ layer.setOpacity(-1.5);
+ layer.setSaturation(-0.7);
+ layer.setVisible(false);
+ expect(layer.getLayerState()).to.eql({
+ brightness: 1,
+ contrast: 0,
+ hue: 42,
+ opacity: 0,
+ saturation: 0,
+ visible: false,
+ ready: true
+ });
+
+ layer.setBrightness(-3);
+ layer.setContrast(42);
+ layer.setHue(-100);
+ layer.setOpacity(3);
+ layer.setSaturation(42);
+ layer.setVisible(true);
+ expect(layer.getLayerState()).to.eql({
+ brightness: -1,
+ contrast: 42,
+ hue: -100,
+ opacity: 1,
+ saturation: 42,
+ visible: true,
+ ready: true
+ });
+ });
+
+ });
+
describe('#setBrightness', function() {
var layer;
diff --git a/test/spec/ol/layer/layergroup.test.js b/test/spec/ol/layer/layergroup.test.js
new file mode 100644
index 0000000000..5d628e3788
--- /dev/null
+++ b/test/spec/ol/layer/layergroup.test.js
@@ -0,0 +1,297 @@
+goog.provide('ol.test.layer.LayerGroup');
+
+describe('ol.layer.LayerGroup', function() {
+
+ describe('constructor (defaults)', function() {
+
+ var layerGroup;
+
+ beforeEach(function() {
+ layerGroup = new ol.layer.LayerGroup();
+ });
+
+ afterEach(function() {
+ goog.dispose(layerGroup);
+ });
+
+ it('creates an instance', function() {
+ expect(layerGroup).to.be.a(ol.layer.LayerGroup);
+ });
+
+ it('provides default brightness', function() {
+ expect(layerGroup.getBrightness()).to.be(0);
+ });
+
+ it('provides default contrast', function() {
+ expect(layerGroup.getContrast()).to.be(1);
+ });
+
+ it('provides default hue', function() {
+ expect(layerGroup.getHue()).to.be(0);
+ });
+
+ it('provides default opacity', function() {
+ expect(layerGroup.getOpacity()).to.be(1);
+ });
+
+ it('provides default saturation', function() {
+ expect(layerGroup.getSaturation()).to.be(1);
+ });
+
+ it('provides default visibility', function() {
+ expect(layerGroup.getVisible()).to.be(true);
+ });
+
+ it('provides default layerState', function() {
+ expect(layerGroup.getLayerState()).to.eql({
+ brightness: 0,
+ contrast: 1,
+ hue: 0,
+ opacity: 1,
+ saturation: 1,
+ visible: true,
+ ready: true
+ });
+ });
+
+ it('provides default empty layers collection', function() {
+ expect(layerGroup.getLayers()).to.be.a(ol.Collection);
+ expect(layerGroup.getLayers().getLength()).to.be(0);
+ });
+
+ });
+
+ describe('constructor (options)', function() {
+
+ it('accepts options', function() {
+ var layer = new ol.layer.Layer({
+ source: new ol.source.Source({
+ projection: 'EPSG:4326'
+ })
+ });
+ var layerGroup = new ol.layer.LayerGroup({
+ layers: [layer],
+ brightness: 0.5,
+ contrast: 10,
+ hue: 180,
+ opacity: 0.5,
+ saturation: 5,
+ visible: false
+ });
+
+ expect(layerGroup.getBrightness()).to.be(0.5);
+ expect(layerGroup.getContrast()).to.be(10);
+ expect(layerGroup.getHue()).to.be(180);
+ expect(layerGroup.getOpacity()).to.be(0.5);
+ expect(layerGroup.getSaturation()).to.be(5);
+ expect(layerGroup.getVisible()).to.be(false);
+ expect(layerGroup.getLayerState()).to.eql({
+ brightness: 0.5,
+ contrast: 10,
+ hue: 180,
+ opacity: 0.5,
+ saturation: 5,
+ visible: false,
+ ready: true
+ });
+ expect(layerGroup.getLayers()).to.be.a(ol.Collection);
+ expect(layerGroup.getLayers().getLength()).to.be(1);
+ expect(layerGroup.getLayers().getAt(0)).to.be(layer);
+
+ goog.dispose(layer);
+ goog.dispose(layerGroup);
+ });
+
+ });
+
+ describe('#getLayerState', function() {
+
+ var layerGroup;
+
+ beforeEach(function() {
+ layerGroup = new ol.layer.LayerGroup();
+ });
+
+ afterEach(function() {
+ goog.dispose(layerGroup);
+ });
+
+ it('returns a layerState from the properties values', function() {
+ layerGroup.setBrightness(-0.7);
+ layerGroup.setContrast(0.3);
+ layerGroup.setHue(-0.3);
+ layerGroup.setOpacity(0.3);
+ layerGroup.setSaturation(0.3);
+ layerGroup.setVisible(false);
+ expect(layerGroup.getLayerState()).to.eql({
+ brightness: -0.7,
+ contrast: 0.3,
+ hue: -0.3,
+ opacity: 0.3,
+ saturation: 0.3,
+ visible: false,
+ ready: true
+ });
+ });
+
+ it('returns a layerState with clamped values', function() {
+ layerGroup.setBrightness(1.5);
+ layerGroup.setContrast(-0.7);
+ layerGroup.setHue(42);
+ layerGroup.setOpacity(-1.5);
+ layerGroup.setSaturation(-0.7);
+ layerGroup.setVisible(false);
+ expect(layerGroup.getLayerState()).to.eql({
+ brightness: 1,
+ contrast: 0,
+ hue: 42,
+ opacity: 0,
+ saturation: 0,
+ visible: false,
+ ready: true
+ });
+
+ layerGroup.setBrightness(-3);
+ layerGroup.setContrast(42);
+ layerGroup.setHue(-100);
+ layerGroup.setOpacity(3);
+ layerGroup.setSaturation(42);
+ layerGroup.setVisible(true);
+ expect(layerGroup.getLayerState()).to.eql({
+ brightness: -1,
+ contrast: 42,
+ hue: -100,
+ opacity: 1,
+ saturation: 42,
+ visible: true,
+ ready: true
+ });
+ });
+
+ });
+
+
+ describe('#setLayers', function() {
+
+ it('sets layers property', function() {
+ var layer = new ol.layer.Layer({
+ source: new ol.source.Source({
+ projection: 'EPSG:4326'
+ })
+ });
+ var layers = new ol.Collection([layer]);
+ var layerGroup = new ol.layer.LayerGroup();
+
+ layerGroup.setLayers(layers);
+ expect(layerGroup.getLayers()).to.be(layers);
+
+ layerGroup.setLayers(null);
+ expect(layerGroup.getLayers()).to.be(null);
+
+ goog.dispose(layerGroup);
+ goog.dispose(layer);
+ goog.dispose(layers);
+ });
+
+ });
+
+
+ describe('#getLayerStatesArray', function() {
+
+ var layerGroup;
+ var layersArray;
+ var layerStatesArray;
+ var obj;
+
+ it('returns an empty array if no layer', function() {
+ layerGroup = new ol.layer.LayerGroup();
+
+ obj = layerGroup.getLayerStatesArray();
+ layersArray = obj.layers;
+ layerStatesArray = obj.layerStates;
+ expect(layersArray).to.be.a(Array);
+ expect(layersArray.length).to.be(0);
+ expect(layerStatesArray).to.be.a(Array);
+ expect(layerStatesArray.length).to.be(0);
+
+ goog.dispose(layerGroup);
+ });
+
+ var layer1 = new ol.layer.Layer({
+ source: new ol.source.Source({
+ projection: 'EPSG:4326'
+ })
+ });
+ var layer2 = new ol.layer.Layer({
+ source: new ol.source.Source({
+ projection: 'EPSG:4326'
+ }),
+ brightness: 0.5,
+ contrast: 10,
+ hue: 180,
+ opacity: 0.5,
+ saturation: 5,
+ visible: false
+ });
+
+ it('does not transform layerStates by default', function() {
+ layerGroup = new ol.layer.LayerGroup({
+ layers: [layer1, layer2]
+ });
+
+ obj = layerGroup.getLayerStatesArray();
+ layersArray = obj.layers;
+ layerStatesArray = obj.layerStates;
+ expect(layersArray).to.be.a(Array);
+ expect(layersArray.length).to.be(2);
+ expect(layersArray[0]).to.be(layer1);
+ expect(layersArray[1]).to.be(layer2);
+ expect(layerStatesArray).to.be.a(Array);
+ expect(layerStatesArray.length).to.be(2);
+ expect(layerStatesArray[0]).to.eql(layer1.getLayerState());
+ expect(layerStatesArray[0]).to.eql(layerGroup.getLayerState());
+ expect(layerStatesArray[1]).to.eql(layer2.getLayerState());
+
+ goog.dispose(layerGroup);
+ });
+
+ it('transforms layerStates correctly', function() {
+ layerGroup = new ol.layer.LayerGroup({
+ layers: [layer1, layer2],
+ brightness: 0.5,
+ contrast: 10,
+ hue: 180,
+ opacity: 0.5,
+ saturation: 5,
+ visible: false
+ });
+
+ obj = layerGroup.getLayerStatesArray();
+ layersArray = obj.layers;
+ layerStatesArray = obj.layerStates;
+ expect(layerStatesArray[0]).to.eql(layerGroup.getLayerState());
+ expect(layerStatesArray[1]).to.eql({
+ brightness: 1,
+ contrast: 100,
+ hue: 360,
+ opacity: 0.25,
+ saturation: 25,
+ visible: false,
+ ready: true
+ });
+
+ goog.dispose(layerGroup);
+ });
+
+ goog.dispose(layer1);
+ goog.dispose(layer2);
+
+ });
+
+});
+
+goog.require('goog.dispose');
+goog.require('ol.layer.Layer');
+goog.require('ol.layer.LayerGroup');
+goog.require('ol.source.Source');
+goog.require('ol.Collection');