/** * @module ol/layer/Layer */ import BaseLayer from './Base.js'; import EventType from '../events/EventType.js'; import LayerProperty from './Property.js'; import RenderEventType from '../render/EventType.js'; import {assert} from '../asserts.js'; import {listen, unlistenByKey} from '../events.js'; /** * @typedef {function(import("../Map.js").FrameState):HTMLElement} RenderFunction */ /*** * @template Return * @typedef {import("../Observable").OnSignature & * import("../Observable").OnSignature & * import("../Observable").OnSignature & * import("../Observable").CombinedOnSignature} LayerOnSignature */ /** * @template {import("../source/Source.js").default} [SourceType=import("../source/Source.js").default] * @typedef {Object} Options * @property {string} [className='ol-layer'] A CSS class name to set to the layer element. * @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 {SourceType} [source] Source for this layer. If not provided to the constructor, * the source can be set by calling {@link module:ol/layer/Layer~Layer#setSource layer.setSource(source)} after * construction. * @property {import("../Map.js").default|null} [map] Map. * @property {RenderFunction} [render] Render function. Takes the frame state as input and is expected to return an * HTML element. Will overwrite the default rendering for the layer. * @property {Object} [properties] Arbitrary observable properties. Can be accessed with `#get()` and `#set()`. */ /** * @typedef {Object} State * @property {import("./Layer.js").default} layer Layer. * @property {number} opacity Opacity, the value is rounded to two digits to appear after the decimal point. * @property {boolean} visible Visible. * @property {boolean} managed Managed. * @property {import("../extent.js").Extent} [extent] Extent. * @property {number} zIndex ZIndex. * @property {number} maxResolution Maximum resolution. * @property {number} minResolution Minimum resolution. * @property {number} minZoom Minimum zoom. * @property {number} maxZoom Maximum zoom. */ /** * @classdesc * Base class from which all layer types are derived. This should only be instantiated * in the case where a custom layer is added to the map with a custom `render` function. * Such a function can be specified in the `options` object, and is expected to return an HTML element. * * A visual representation of raster or vector map data. * Layers group together those properties that pertain to how the data is to be * displayed, irrespective of the source of that data. * * Layers are usually added to a map with [map.addLayer()]{@link import("../Map.js").default#addLayer}. * Components like {@link module:ol/interaction/Draw~Draw} use unmanaged layers * internally. These unmanaged layers are associated with the map using * [layer.setMap()]{@link module:ol/layer/Layer~Layer#setMap} instead. * * A generic `change` event is fired when the state of the source changes. * * @fires import("../render/Event.js").RenderEvent#prerender * @fires import("../render/Event.js").RenderEvent#postrender * * @template {import("../source/Source.js").default} [SourceType=import("../source/Source.js").default] * @template {import("../renderer/Layer.js").default} [RendererType=import("../renderer/Layer.js").default] * @api */ class Layer extends BaseLayer { /** * @param {Options} options Layer options. */ constructor(options) { const baseOptions = Object.assign({}, options); delete baseOptions.source; super(baseOptions); /*** * @type {LayerOnSignature} */ this.on; /*** * @type {LayerOnSignature} */ this.once; /*** * @type {LayerOnSignature} */ this.un; /** * @private * @type {?import("../events.js").EventsKey} */ this.mapPrecomposeKey_ = null; /** * @private * @type {?import("../events.js").EventsKey} */ this.mapRenderKey_ = null; /** * @private * @type {?import("../events.js").EventsKey} */ this.sourceChangeKey_ = null; /** * @private * @type {RendererType} */ this.renderer_ = null; /** * @protected * @type {boolean} */ this.rendered = false; // Overwrite default render method with a custom one if (options.render) { this.render = options.render; } if (options.map) { this.setMap(options.map); } this.addChangeListener( LayerProperty.SOURCE, this.handleSourcePropertyChange_ ); const source = options.source ? /** @type {SourceType} */ (options.source) : null; this.setSource(source); } /** * @param {Array} [array] Array of layers (to be modified in place). * @return {Array} Array of layers. */ getLayersArray(array) { array = array ? array : []; array.push(this); return array; } /** * @param {Array} [states] Optional list of layer states (to be modified in place). * @return {Array} List of layer states. */ getLayerStatesArray(states) { states = states ? states : []; states.push(this.getLayerState()); return states; } /** * Get the layer source. * @return {SourceType|null} The layer source (or `null` if not yet set). * @observable * @api */ getSource() { return /** @type {SourceType} */ (this.get(LayerProperty.SOURCE)) || null; } /** * @return {SourceType|null} The source being rendered. */ getRenderSource() { return this.getSource(); } /** * @return {import("../source/Source.js").State} Source state. */ getSourceState() { const source = this.getSource(); return !source ? 'undefined' : source.getState(); } /** * @private */ handleSourceChange_() { this.changed(); } /** * @private */ handleSourcePropertyChange_() { if (this.sourceChangeKey_) { unlistenByKey(this.sourceChangeKey_); this.sourceChangeKey_ = null; } const source = this.getSource(); if (source) { this.sourceChangeKey_ = listen( source, EventType.CHANGE, this.handleSourceChange_, this ); } this.changed(); } /** * @param {import("../pixel").Pixel} pixel Pixel. * @return {Promise>} Promise that resolves with * an array of features. */ getFeatures(pixel) { if (!this.renderer_) { return new Promise((resolve) => resolve([])); } return this.renderer_.getFeatures(pixel); } /** * @param {import("../pixel").Pixel} pixel Pixel. * @return {Uint8ClampedArray|Uint8Array|Float32Array|DataView|null} Pixel data. */ getData(pixel) { if (!this.renderer_ || !this.rendered) { return null; } return this.renderer_.getData(pixel); } /** * In charge to manage the rendering of the layer. One layer type is * bounded with one layer renderer. * @param {?import("../Map.js").FrameState} frameState Frame state. * @param {HTMLElement} target Target which the renderer may (but need not) use * for rendering its content. * @return {HTMLElement} The rendered element. */ render(frameState, target) { const layerRenderer = this.getRenderer(); if (layerRenderer.prepareFrame(frameState)) { this.rendered = true; return layerRenderer.renderFrame(frameState, target); } } /** * Called when a layer is not visible during a map render. */ unrender() { this.rendered = false; } /** * For use inside the library only. * @param {import("../Map.js").default|null} map Map. */ setMapInternal(map) { if (!map) { this.unrender(); } this.set(LayerProperty.MAP, map); } /** * For use inside the library only. * @return {import("../Map.js").default|null} Map. */ getMapInternal() { return this.get(LayerProperty.MAP); } /** * Sets the layer to be rendered on top of other layers on a map. The map will * not manage this layer in its layers collection. This * is useful for temporary layers. To remove an unmanaged layer from the map, * use `#setMap(null)`. * * To add the layer to a map and have it managed by the map, use * {@link module:ol/Map~Map#addLayer} instead. * @param {import("../Map.js").default|null} map Map. * @api */ setMap(map) { if (this.mapPrecomposeKey_) { unlistenByKey(this.mapPrecomposeKey_); this.mapPrecomposeKey_ = null; } if (!map) { this.changed(); } if (this.mapRenderKey_) { unlistenByKey(this.mapRenderKey_); this.mapRenderKey_ = null; } if (map) { this.mapPrecomposeKey_ = listen( map, RenderEventType.PRECOMPOSE, function (evt) { const renderEvent = /** @type {import("../render/Event.js").default} */ (evt); const layerStatesArray = renderEvent.frameState.layerStatesArray; const layerState = this.getLayerState(false); // A layer can only be added to the map once. Use either `layer.setMap()` or `map.addLayer()`, not both. assert( !layerStatesArray.some(function (arrayLayerState) { return arrayLayerState.layer === layerState.layer; }), 67 ); layerStatesArray.push(layerState); }, this ); this.mapRenderKey_ = listen(this, EventType.CHANGE, map.render, map); this.changed(); } } /** * Set the layer source. * @param {SourceType|null} source The layer source. * @observable * @api */ setSource(source) { this.set(LayerProperty.SOURCE, source); } /** * Get the renderer for this layer. * @return {RendererType|null} The layer renderer. */ getRenderer() { if (!this.renderer_) { this.renderer_ = this.createRenderer(); } return this.renderer_; } /** * @return {boolean} The layer has a renderer. */ hasRenderer() { return !!this.renderer_; } /** * Create a renderer for this layer. * @return {RendererType} A layer renderer. * @protected */ createRenderer() { return null; } /** * Clean up. */ disposeInternal() { if (this.renderer_) { this.renderer_.dispose(); delete this.renderer_; } this.setSource(null); super.disposeInternal(); } } /** * Return `true` if the layer is visible and if the provided view state * has resolution and zoom levels that are in range of the layer's min/max. * @param {State} layerState Layer state. * @param {import("../View.js").State} viewState View state. * @return {boolean} The layer is visible at the given view state. */ export function inView(layerState, viewState) { if (!layerState.visible) { return false; } const resolution = viewState.resolution; if ( resolution < layerState.minResolution || resolution >= layerState.maxResolution ) { return false; } const zoom = viewState.zoom; return zoom > layerState.minZoom && zoom <= layerState.maxZoom; } export default Layer;