From 097ce52060669c3bd202fa8aa89299cf0a4bde6e Mon Sep 17 00:00:00 2001 From: Bruno Binet Date: Fri, 7 Jun 2013 02:08:25 +0200 Subject: [PATCH 01/10] Add layergroup support --- src/objectliterals.jsdoc | 23 ++- src/ol/layer/layer.js | 259 +++------------------------------- src/ol/layer/layerbase.js | 277 +++++++++++++++++++++++++++++++++++++ src/ol/layer/layergroup.js | 210 ++++++++++++++++++++++++++++ src/ol/map.js | 87 +++++------- 5 files changed, 561 insertions(+), 295 deletions(-) create mode 100644 src/ol/layer/layerbase.js create mode 100644 src/ol/layer/layergroup.js 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..c313c3c5c0 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,20 @@ 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)); -}; -goog.exportProperty( - ol.layer.Layer.prototype, - 'getBrightness', - ol.layer.Layer.prototype.getBrightness); - - -/** - * @return {number} Contrast. - */ -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 +68,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 +77,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..30aa53b8f1 --- /dev/null +++ b/src/ol/layer/layerbase.js @@ -0,0 +1,277 @@ +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 {{ + * 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.js b/src/ol/layer/layergroup.js new file mode 100644 index 0000000000..fb839a4dac --- /dev/null +++ b/src/ol/layer/layergroup.js @@ -0,0 +1,210 @@ +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.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/map.js b/src/ol/map.js index 3dded4b5d5..563b958b42 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'); @@ -56,6 +54,8 @@ 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'); @@ -309,9 +309,12 @@ ol.Map = function(options) { /** * @private - * @type {Array.} + * @type {ol.layer.LayerGroup} */ - this.layersListenerKeys_ = null; + this.layerGroup_ = new ol.layer.LayerGroup(); + + goog.events.listen(this.layerGroup_, ol.ObjectEventType.CHANGE, + this.handleLayerGroupChanged_, false, this); goog.events.listen(this, ol.Object.getChangeEventType(ol.MapProperty.LAYERS), this.handleLayersChanged_, false, this); @@ -352,7 +355,7 @@ 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(); @@ -615,46 +618,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 +751,23 @@ ol.Map.prototype.handleViewChanged_ = function() { }; +/** + * @param {goog.events.Event} event Event. + * @private + */ +ol.Map.prototype.handleLayersChanged_ = function(event) { + this.layerGroup_.setLayers(this.getLayers()); +}; + + +/** + * @private + */ +ol.Map.prototype.handleLayerGroupChanged_ = function() { + this.render(); +}; + + /** * @return {boolean} Is defined. */ @@ -845,14 +825,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(); 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 +849,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.layerGroup_.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 = { From 1d15d840ac1214112a2d18d51d6f0d9fb3ef99a5 Mon Sep 17 00:00:00 2001 From: Bruno Binet Date: Wed, 12 Jun 2013 13:34:26 +0200 Subject: [PATCH 02/10] Add layergroup exports --- src/ol/layer/layergroup.exports | 1 + 1 file changed, 1 insertion(+) create mode 100644 src/ol/layer/layergroup.exports 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 From 31c8644716fea434adbd80af5ea3866f66127382 Mon Sep 17 00:00:00 2001 From: Bruno Binet Date: Thu, 13 Jun 2013 15:23:03 +0200 Subject: [PATCH 03/10] Add tests for ol.layer.LayerGroup --- test/spec/ol/layer/layergroup.test.js | 297 ++++++++++++++++++++++++++ 1 file changed, 297 insertions(+) create mode 100644 test/spec/ol/layer/layergroup.test.js 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'); From 1aa695580bacfae53b7ee2c4a55cabd333d47153 Mon Sep 17 00:00:00 2001 From: Bruno Binet Date: Thu, 13 Jun 2013 17:45:31 +0200 Subject: [PATCH 04/10] Add tests for layer.getLayerState() --- test/spec/ol/layer/layer.test.js | 91 ++++++++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) 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; From f3b63d9c2c9d47410ecf02583b5e759f9bff77d7 Mon Sep 17 00:00:00 2001 From: Bruno Binet Date: Tue, 11 Jun 2013 18:10:06 +0200 Subject: [PATCH 05/10] Add a layer-group example --- examples/layer-group.html | 99 +++++++++++++++++++++++++++++++++++++++ examples/layer-group.js | 59 +++++++++++++++++++++++ 2 files changed, 158 insertions(+) create mode 100644 examples/layer-group.html create mode 100644 examples/layer-group.js diff --git a/examples/layer-group.html b/examples/layer-group.html new file mode 100644 index 0000000000..3b966c954e --- /dev/null +++ b/examples/layer-group.html @@ -0,0 +1,99 @@ + + + + + + + + + + + Layer group example + + + + + +
+ +
+
+
+ +

Layer group example

+

Example of a map with layer group.

+
+

See the layer-group.js source to see how this is done.

+
+
tilejson, input, bind, group, layergroup
+
+ +
+
Click on layer nodes below to change their properties.
+
    +
  • OpenAerial layer +
    + + + +
    +
  • +
  • Layer group +
    + + + +
    +
      +
    • Food insecurity layer +
      + + + +
      +
    • +
    • World borders layer +
      + + + +
      +
    • +
    +
  • +
+ +
+ +
+ + + + + + + diff --git a/examples/layer-group.js b/examples/layer-group.js new file mode 100644 index 0000000000..18055859e5 --- /dev/null +++ b/examples/layer-group.js @@ -0,0 +1,59 @@ +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'); + new ol.dom.Input($(layerid + ' .opacity')[0]) + .bindTo('value', layer, 'opacity'); +} +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(); From 1100cc67da4d4a09e253ddc15ab4b0ecafd69caf Mon Sep 17 00:00:00 2001 From: Bruno Binet Date: Wed, 12 Jun 2013 10:29:50 +0200 Subject: [PATCH 06/10] Get opacity from layerState in webglmaprenderer instead of getting it from layer.getOpacity(). Fix the issue of changing opacity which didn't work with LayerGroups in webgl. --- src/ol/renderer/webgl/webglmaprenderer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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); From d84477c1c003af1da5a956435026691c962436ff Mon Sep 17 00:00:00 2001 From: Bruno Binet Date: Thu, 13 Jun 2013 14:17:29 +0200 Subject: [PATCH 07/10] Bind more layer properties in layer-group example This allows to update hue, saturation, brightness, contrast values. --- examples/layer-group.html | 32 ++++++++++++++++++++++++++++++++ examples/layer-group.js | 7 +++++-- 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/examples/layer-group.html b/examples/layer-group.html index 3b966c954e..747d0b4c44 100644 --- a/examples/layer-group.html +++ b/examples/layer-group.html @@ -54,6 +54,14 @@ + + + + + + + +
  • Layer group @@ -63,6 +71,14 @@ + + + + + + + +
    • Food insecurity layer @@ -72,6 +88,14 @@ + + + + + + + +
    • World borders layer @@ -81,6 +105,14 @@ + + + + + + + +
    diff --git a/examples/layer-group.js b/examples/layer-group.js index 18055859e5..183e8fa9bc 100644 --- a/examples/layer-group.js +++ b/examples/layer-group.js @@ -42,8 +42,11 @@ var map = new ol.Map({ function bindInputs(layerid, layer) { new ol.dom.Input($(layerid + ' .visible')[0]) .bindTo('checked', layer, 'visible'); - new ol.dom.Input($(layerid + ' .opacity')[0]) - .bindTo('value', layer, 'opacity'); + $.each(['opacity', 'hue', 'saturation', 'contrast', 'brightness'], + function(i, v) { + new ol.dom.Input($(layerid + ' .' + v)[0]).bindTo('value', layer, v); + } + ); } map.getLayers().forEach(function(layer, i) { bindInputs('#layer' + i, layer); From e4c36378acf1d41bbf39ccd636f56492818b4f37 Mon Sep 17 00:00:00 2001 From: Bruno Binet Date: Thu, 27 Jun 2013 18:58:32 +0200 Subject: [PATCH 08/10] ol.Map now have a LAYERGROUP property The LAYERS property is gone. --- src/ol/layer/layer.js | 10 +++++ src/ol/layer/layerbase.js | 8 ++++ src/ol/layer/layergroup.js | 12 +++++ src/ol/map.exports | 1 + src/ol/map.js | 92 +++++++++++++++++++++----------------- 5 files changed, 81 insertions(+), 42 deletions(-) diff --git a/src/ol/layer/layer.js b/src/ol/layer/layer.js index c313c3c5c0..2e331ff189 100644 --- a/src/ol/layer/layer.js +++ b/src/ol/layer/layer.js @@ -45,6 +45,16 @@ ol.layer.Layer.prototype.dispatchLoadEvent_ = function() { }; +/** + * @inheritDoc + */ +ol.layer.Layer.prototype.getLayersArray = function(opt_array) { + var array = (goog.isDef(opt_array)) ? opt_array : []; + array.push(this); + return array; +}; + + /** * @inheritDoc */ diff --git a/src/ol/layer/layerbase.js b/src/ol/layer/layerbase.js index 30aa53b8f1..1b7d0abdcd 100644 --- a/src/ol/layer/layerbase.js +++ b/src/ol/layer/layerbase.js @@ -122,6 +122,14 @@ ol.layer.LayerBase.prototype.getLayerState = function() { }; +/** + * @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., diff --git a/src/ol/layer/layergroup.js b/src/ol/layer/layergroup.js index fb839a4dac..85dd6c85bd 100644 --- a/src/ol/layer/layergroup.js +++ b/src/ol/layer/layergroup.js @@ -167,6 +167,18 @@ goog.exportProperty( 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 */ 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 563b958b42..8fa48bedfe 100644 --- a/src/ol/map.js +++ b/src/ol/map.js @@ -53,7 +53,6 @@ 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'); @@ -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,17 +312,9 @@ ol.Map = function(options) { goog.bind(this.getTilePriority, this), goog.bind(this.handleTileChange_, this)); - /** - * @private - * @type {ol.layer.LayerGroup} - */ - this.layerGroup_ = new ol.layer.LayerGroup(); - - goog.events.listen(this.layerGroup_, ol.ObjectEventType.CHANGE, + goog.events.listen( + this, ol.Object.getChangeEventType(ol.MapProperty.LAYERGROUP), this.handleLayerGroupChanged_, false, this); - - goog.events.listen(this, ol.Object.getChangeEventType(ol.MapProperty.LAYERS), - this.handleLayersChanged_, 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), @@ -358,7 +355,7 @@ ol.Map.prototype.addControl = function(control) { * @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); }; @@ -468,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); }; @@ -481,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); }; @@ -497,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); /** @@ -755,8 +762,8 @@ ol.Map.prototype.handleViewChanged_ = function() { * @param {goog.events.Event} event Event. * @private */ -ol.Map.prototype.handleLayersChanged_ = function(event) { - this.layerGroup_.setLayers(this.getLayers()); +ol.Map.prototype.handleLayerGroupPropertyChanged_ = function(event) { + this.render(); }; @@ -764,6 +771,16 @@ ol.Map.prototype.handleLayersChanged_ = function(event) { * @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(); }; @@ -830,7 +847,7 @@ ol.Map.prototype.removeControl = function(control) { * 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.LayerBase|undefined} */ (layers.remove(layer)); }; @@ -855,7 +872,7 @@ ol.Map.prototype.renderFrame_ = function(time) { var frameState = null; if (goog.isDef(size) && goog.isDef(view2D) && view2D.isDef()) { var viewHints = view.getHints(); - var obj = this.layerGroup_.getLayerStatesArray(); + var obj = this.getLayerGroup().getLayerStatesArray(); var layersArray = obj.layers; var layerStatesArray = obj.layerStates; var layerStates = {}; @@ -926,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); /** @@ -1042,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; From 3ecb5b89ff919d0cebec1ebb156e9b0341b08853 Mon Sep 17 00:00:00 2001 From: Bruno Binet Date: Thu, 1 Aug 2013 17:36:46 +0200 Subject: [PATCH 09/10] Create a custom event type for vector layers Vector layers will now dispatch ol.layer.VectorLayerEventType.ADD and ol.layer.VectorLayerEventType.REMOVE event types instead of the generic goog.events.EventType.CHANGE event type. This will fix a maximum call stack size exceeded javascript error. --- src/ol/layer/vectorlayer.js | 18 +++++++++++++----- .../canvas/canvasvectorlayerrenderer.js | 9 ++++++--- 2 files changed, 19 insertions(+), 8 deletions(-) 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/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 From 54bf8c2d2ac224fdd999a6922f2cedfcd8efe03b Mon Sep 17 00:00:00 2001 From: Bruno Binet Date: Tue, 13 Aug 2013 12:36:48 +0200 Subject: [PATCH 10/10] Bindto valueAsNumber in layer-group example (now that #898 is in) --- examples/layer-group.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/layer-group.js b/examples/layer-group.js index 183e8fa9bc..422d55d579 100644 --- a/examples/layer-group.js +++ b/examples/layer-group.js @@ -44,7 +44,8 @@ function bindInputs(layerid, layer) { .bindTo('checked', layer, 'visible'); $.each(['opacity', 'hue', 'saturation', 'contrast', 'brightness'], function(i, v) { - new ol.dom.Input($(layerid + ' .' + v)[0]).bindTo('value', layer, v); + new ol.dom.Input($(layerid + ' .' + v)[0]) + .bindTo('valueAsNumber', layer, v); } ); }