/** * @module ol/layer/Group */ import BaseLayer from './Base.js'; import Collection from '../Collection.js'; import CollectionEventType from '../CollectionEventType.js'; import EventType from '../events/EventType.js'; import ObjectEventType from '../ObjectEventType.js'; import SourceState from '../source/State.js'; import {assert} from '../asserts.js'; import {assign, clear} from '../obj.js'; import {getIntersection} from '../extent.js'; import {getUid} from '../util.js'; import {listen, unlistenByKey} from '../events.js'; /*** * @template Return * @typedef {import("../Observable").OnSignature & * import("../Observable").OnSignature & * import("../Observable").CombinedOnSignature} GroupOnSignature */ /** * @typedef {Object} Options * @property {number} [opacity=1] Opacity (0, 1). * @property {boolean} [visible=true] Visibility. * @property {import("../extent.js").Extent} [extent] The bounding extent for layer rendering. The layer will not be * rendered outside of this extent. * @property {number} [zIndex] The z-index for layer rendering. At rendering time, the layers * will be ordered, first by Z-index and then by position. When `undefined`, a `zIndex` of 0 is assumed * for layers that are added to the map's `layers` collection, or `Infinity` when the layer's `setMap()` * method was used. * @property {number} [minResolution] The minimum resolution (inclusive) at which this layer will be * visible. * @property {number} [maxResolution] The maximum resolution (exclusive) below which this layer will * be visible. * @property {number} [minZoom] The minimum view zoom level (exclusive) above which this layer will be * visible. * @property {number} [maxZoom] The maximum view zoom level (inclusive) at which this layer will * be visible. * @property {Array|import("../Collection.js").default} [layers] Child layers. * @property {Object} [properties] Arbitrary observable properties. Can be accessed with `#get()` and `#set()`. */ /** * @enum {string} * @private */ const Property = { LAYERS: 'layers', }; /** * @classdesc * A {@link module:ol/Collection~Collection} of layers that are handled together. * * A generic `change` event is triggered when the group/Collection changes. * * @api */ class LayerGroup extends BaseLayer { /** * @param {Options} [opt_options] Layer options. */ constructor(opt_options) { const options = opt_options || {}; const baseOptions = /** @type {Options} */ (assign({}, options)); delete baseOptions.layers; let layers = options.layers; super(baseOptions); /*** * @type {GroupOnSignature} */ this.on; /*** * @type {GroupOnSignature} */ this.once; /*** * @type {GroupOnSignature} */ this.un; /** * @private * @type {Array} */ this.layersListenerKeys_ = []; /** * @private * @type {Object>} */ this.listenerKeys_ = {}; this.addChangeListener(Property.LAYERS, this.handleLayersChanged_); if (layers) { if (Array.isArray(layers)) { layers = new Collection(layers.slice(), {unique: true}); } else { assert(typeof (/** @type {?} */ (layers).getArray) === 'function', 43); // Expected `layers` to be an array or a `Collection` } } else { layers = new Collection(undefined, {unique: true}); } this.setLayers(layers); } /** * @private */ handleLayerChange_() { this.changed(); } /** * @private */ handleLayersChanged_() { this.layersListenerKeys_.forEach(unlistenByKey); this.layersListenerKeys_.length = 0; const layers = this.getLayers(); this.layersListenerKeys_.push( listen(layers, CollectionEventType.ADD, this.handleLayersAdd_, this), listen(layers, CollectionEventType.REMOVE, this.handleLayersRemove_, this) ); for (const id in this.listenerKeys_) { this.listenerKeys_[id].forEach(unlistenByKey); } clear(this.listenerKeys_); const layersArray = layers.getArray(); for (let i = 0, ii = layersArray.length; i < ii; i++) { const layer = layersArray[i]; this.listenerKeys_[getUid(layer)] = [ listen( layer, ObjectEventType.PROPERTYCHANGE, this.handleLayerChange_, this ), listen(layer, EventType.CHANGE, this.handleLayerChange_, this), ]; } this.changed(); } /** * @param {import("../Collection.js").CollectionEvent} collectionEvent CollectionEvent. * @private */ handleLayersAdd_(collectionEvent) { const layer = /** @type {import("./Base.js").default} */ ( collectionEvent.element ); this.listenerKeys_[getUid(layer)] = [ listen( layer, ObjectEventType.PROPERTYCHANGE, this.handleLayerChange_, this ), listen(layer, EventType.CHANGE, this.handleLayerChange_, this), ]; this.changed(); } /** * @param {import("../Collection.js").CollectionEvent} collectionEvent CollectionEvent. * @private */ handleLayersRemove_(collectionEvent) { const layer = /** @type {import("./Base.js").default} */ ( collectionEvent.element ); const key = getUid(layer); this.listenerKeys_[key].forEach(unlistenByKey); delete this.listenerKeys_[key]; this.changed(); } /** * Returns the {@link module:ol/Collection collection} of {@link module:ol/layer/Layer~Layer layers} * in this group. * @return {!import("../Collection.js").default} Collection of * {@link module:ol/layer/Base layers} that are part of this group. * @observable * @api */ getLayers() { return /** @type {!import("../Collection.js").default} */ ( this.get(Property.LAYERS) ); } /** * Set the {@link module:ol/Collection collection} of {@link module:ol/layer/Layer~Layer layers} * in this group. * @param {!import("../Collection.js").default} layers Collection of * {@link module:ol/layer/Base layers} that are part of this group. * @observable * @api */ setLayers(layers) { this.set(Property.LAYERS, layers); } /** * @param {Array} [opt_array] Array of layers (to be modified in place). * @return {Array} Array of layers. */ getLayersArray(opt_array) { const array = opt_array !== undefined ? opt_array : []; this.getLayers().forEach(function (layer) { layer.getLayersArray(array); }); return array; } /** * Get the layer states list and use this groups z-index as the default * for all layers in this and nested groups, if it is unset at this point. * If opt_states is not provided and this group's z-index is undefined * 0 is used a the default z-index. * @param {Array} [opt_states] Optional list * of layer states (to be modified in place). * @return {Array} List of layer states. */ getLayerStatesArray(opt_states) { const states = opt_states !== undefined ? opt_states : []; const pos = states.length; this.getLayers().forEach(function (layer) { layer.getLayerStatesArray(states); }); const ownLayerState = this.getLayerState(); let defaultZIndex = ownLayerState.zIndex; if (!opt_states && ownLayerState.zIndex === undefined) { defaultZIndex = 0; } for (let i = pos, ii = states.length; i < ii; i++) { const layerState = states[i]; layerState.opacity *= ownLayerState.opacity; layerState.visible = layerState.visible && ownLayerState.visible; layerState.maxResolution = Math.min( layerState.maxResolution, ownLayerState.maxResolution ); layerState.minResolution = Math.max( layerState.minResolution, ownLayerState.minResolution ); layerState.minZoom = Math.max(layerState.minZoom, ownLayerState.minZoom); layerState.maxZoom = Math.min(layerState.maxZoom, ownLayerState.maxZoom); if (ownLayerState.extent !== undefined) { if (layerState.extent !== undefined) { layerState.extent = getIntersection( layerState.extent, ownLayerState.extent ); } else { layerState.extent = ownLayerState.extent; } } if (layerState.zIndex === undefined) { layerState.zIndex = defaultZIndex; } } return states; } /** * @return {import("../source/State.js").default} Source state. */ getSourceState() { return SourceState.READY; } } export default LayerGroup;