UNPKG

ol

Version:

OpenLayers mapping library

481 lines • 18 kB
var __extends = (this && this.__extends) || (function () { var extendStatics = function (d, b) { extendStatics = Object.setPrototypeOf || ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; return extendStatics(d, b); }; return function (d, b) { extendStatics(d, b); function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; })(); /** * @module ol/source/Raster */ import ImageCanvas from '../ImageCanvas.js'; import TileQueue from '../TileQueue.js'; import { createCanvasContext2D } from '../dom.js'; import Event from '../events/Event.js'; import EventType from '../events/EventType.js'; import { Processor } from 'pixelworks/lib/index.js'; import { equals, getCenter, getHeight, getWidth } from '../extent.js'; import ImageLayer from '../layer/Image.js'; import TileLayer from '../layer/Tile.js'; import { assign } from '../obj.js'; import { create as createTransform } from '../transform.js'; import ImageSource from './Image.js'; import TileSource from './Tile.js'; import SourceState from './State.js'; import Source from './Source.js'; /** * A function that takes an array of input data, performs some operation, and * returns an array of output data. * For `pixel` type operations, the function will be called with an array of * pixels, where each pixel is an array of four numbers (`[r, g, b, a]`) in the * range of 0 - 255. It should return a single pixel array. * For `'image'` type operations, functions will be called with an array of * {@link ImageData https://developer.mozilla.org/en-US/docs/Web/API/ImageData} * and should return a single {@link ImageData * https://developer.mozilla.org/en-US/docs/Web/API/ImageData}. The operations * are called with a second "data" argument, which can be used for storage. The * data object is accessible from raster events, where it can be initialized in * "beforeoperations" and accessed again in "afteroperations". * * @typedef {function((Array<Array<number>>|Array<ImageData>), Object): * (Array<number>|ImageData)} Operation */ /** * @enum {string} */ var RasterEventType = { /** * Triggered before operations are run. * @event module:ol/source/Raster.RasterSourceEvent#beforeoperations * @api */ BEFOREOPERATIONS: 'beforeoperations', /** * Triggered after operations are run. * @event module:ol/source/Raster.RasterSourceEvent#afteroperations * @api */ AFTEROPERATIONS: 'afteroperations' }; /** * Raster operation type. Supported values are `'pixel'` and `'image'`. * @enum {string} */ var RasterOperationType = { PIXEL: 'pixel', IMAGE: 'image' }; /** * @classdesc * Events emitted by {@link module:ol/source/Raster} instances are instances of this * type. */ var RasterSourceEvent = /** @class */ (function (_super) { __extends(RasterSourceEvent, _super); /** * @param {string} type Type. * @param {import("../PluggableMap.js").FrameState} frameState The frame state. * @param {Object} data An object made available to operations. */ function RasterSourceEvent(type, frameState, data) { var _this = _super.call(this, type) || this; /** * The raster extent. * @type {import("../extent.js").Extent} * @api */ _this.extent = frameState.extent; /** * The pixel resolution (map units per pixel). * @type {number} * @api */ _this.resolution = frameState.viewState.resolution / frameState.pixelRatio; /** * An object made available to all operations. This can be used by operations * as a storage object (e.g. for calculating statistics). * @type {Object} * @api */ _this.data = data; return _this; } return RasterSourceEvent; }(Event)); export { RasterSourceEvent }; /** * @typedef {Object} Options * @property {Array<import("./Source.js").default|import("../layer/Layer.js").default>} sources Input * sources or layers. For vector data, use an VectorImage layer. * @property {Operation} [operation] Raster operation. * The operation will be called with data from input sources * and the output will be assigned to the raster source. * @property {Object} [lib] Functions that will be made available to operations run in a worker. * @property {number} [threads] By default, operations will be run in a single worker thread. * To avoid using workers altogether, set `threads: 0`. For pixel operations, operations can * be run in multiple worker threads. Note that there is additional overhead in * transferring data to multiple workers, and that depending on the user's * system, it may not be possible to parallelize the work. * @property {RasterOperationType} [operationType='pixel'] Operation type. * Supported values are `'pixel'` and `'image'`. By default, * `'pixel'` operations are assumed, and operations will be called with an * array of pixels from input sources. If set to `'image'`, operations will * be called with an array of ImageData objects from input sources. */ /** * @classdesc * A source that transforms data from any number of input sources using an * {@link module:ol/source/Raster~Operation} function to transform input pixel values into * output pixel values. * * @fires module:ol/source/Raster.RasterSourceEvent * @api */ var RasterSource = /** @class */ (function (_super) { __extends(RasterSource, _super); /** * @param {Options} options Options. */ function RasterSource(options) { var _this = _super.call(this, { projection: null }) || this; /** * @private * @type {*} */ _this.worker_ = null; /** * @private * @type {RasterOperationType} */ _this.operationType_ = options.operationType !== undefined ? options.operationType : RasterOperationType.PIXEL; /** * @private * @type {number} */ _this.threads_ = options.threads !== undefined ? options.threads : 1; /** * @private * @type {Array<import("../layer/Layer.js").default>} */ _this.layers_ = createLayers(options.sources); var changed = _this.changed.bind(_this); for (var i = 0, ii = _this.layers_.length; i < ii; ++i) { _this.layers_[i].addEventListener(EventType.CHANGE, changed); } /** * @private * @type {import("../TileQueue.js").default} */ _this.tileQueue_ = new TileQueue(function () { return 1; }, _this.changed.bind(_this)); /** * The most recently requested frame state. * @type {import("../PluggableMap.js").FrameState} * @private */ _this.requestedFrameState_; /** * The most recently rendered image canvas. * @type {import("../ImageCanvas.js").default} * @private */ _this.renderedImageCanvas_ = null; /** * The most recently rendered revision. * @type {number} */ _this.renderedRevision_; /** * @private * @type {import("../PluggableMap.js").FrameState} */ _this.frameState_ = { animate: false, coordinateToPixelTransform: createTransform(), extent: null, index: 0, layerIndex: 0, layerStatesArray: getLayerStatesArray(_this.layers_), pixelRatio: 1, pixelToCoordinateTransform: createTransform(), postRenderFunctions: [], size: [0, 0], tileQueue: _this.tileQueue_, time: Date.now(), usedTiles: {}, viewState: /** @type {import("../View.js").State} */ ({ rotation: 0 }), viewHints: [], wantedTiles: {}, declutterItems: [] }; _this.setAttributions(function (frameState) { var attributions = []; for (var index = 0, iMax = options.sources.length; index < iMax; ++index) { var sourceOrLayer = options.sources[index]; var source = sourceOrLayer instanceof Source ? sourceOrLayer : sourceOrLayer.getSource(); var attributionGetter = source.getAttributions(); if (typeof attributionGetter === 'function') { var sourceAttribution = attributionGetter(frameState); attributions.push.apply(attributions, sourceAttribution); } } return attributions.length !== 0 ? attributions : null; }); if (options.operation !== undefined) { _this.setOperation(options.operation, options.lib); } return _this; } /** * Set the operation. * @param {Operation} operation New operation. * @param {Object=} opt_lib Functions that will be available to operations run * in a worker. * @api */ RasterSource.prototype.setOperation = function (operation, opt_lib) { this.worker_ = new Processor({ operation: operation, imageOps: this.operationType_ === RasterOperationType.IMAGE, queue: 1, lib: opt_lib, threads: this.threads_ }); this.changed(); }; /** * Update the stored frame state. * @param {import("../extent.js").Extent} extent The view extent (in map units). * @param {number} resolution The view resolution. * @param {import("../proj/Projection.js").default} projection The view projection. * @return {import("../PluggableMap.js").FrameState} The updated frame state. * @private */ RasterSource.prototype.updateFrameState_ = function (extent, resolution, projection) { var frameState = /** @type {import("../PluggableMap.js").FrameState} */ (assign({}, this.frameState_)); frameState.viewState = /** @type {import("../View.js").State} */ (assign({}, frameState.viewState)); var center = getCenter(extent); frameState.extent = extent.slice(); frameState.size[0] = Math.round(getWidth(extent) / resolution); frameState.size[1] = Math.round(getHeight(extent) / resolution); frameState.time = Infinity; var viewState = frameState.viewState; viewState.center = center; viewState.projection = projection; viewState.resolution = resolution; return frameState; }; /** * Determine if all sources are ready. * @return {boolean} All sources are ready. * @private */ RasterSource.prototype.allSourcesReady_ = function () { var ready = true; var source; for (var i = 0, ii = this.layers_.length; i < ii; ++i) { source = this.layers_[i].getSource(); if (source.getState() !== SourceState.READY) { ready = false; break; } } return ready; }; /** * @inheritDoc */ RasterSource.prototype.getImage = function (extent, resolution, pixelRatio, projection) { if (!this.allSourcesReady_()) { return null; } var frameState = this.updateFrameState_(extent, resolution, projection); this.requestedFrameState_ = frameState; // check if we can't reuse the existing ol/ImageCanvas if (this.renderedImageCanvas_) { var renderedResolution = this.renderedImageCanvas_.getResolution(); var renderedExtent = this.renderedImageCanvas_.getExtent(); if (resolution !== renderedResolution || !equals(extent, renderedExtent)) { this.renderedImageCanvas_ = null; } } if (!this.renderedImageCanvas_ || this.getRevision() !== this.renderedRevision_) { this.processSources_(); } frameState.tileQueue.loadMoreTiles(16, 16); if (frameState.animate) { requestAnimationFrame(this.changed.bind(this)); } return this.renderedImageCanvas_; }; /** * Start processing source data. * @private */ RasterSource.prototype.processSources_ = function () { var frameState = this.requestedFrameState_; var len = this.layers_.length; var imageDatas = new Array(len); for (var i = 0; i < len; ++i) { frameState.layerIndex = i; var imageData = getImageData(this.layers_[i], frameState); if (imageData) { imageDatas[i] = imageData; } else { return; } } var data = {}; this.dispatchEvent(new RasterSourceEvent(RasterEventType.BEFOREOPERATIONS, frameState, data)); this.worker_.process(imageDatas, data, this.onWorkerComplete_.bind(this, frameState)); }; /** * Called when pixel processing is complete. * @param {import("../PluggableMap.js").FrameState} frameState The frame state. * @param {Error} err Any error during processing. * @param {ImageData} output The output image data. * @param {Object} data The user data. * @private */ RasterSource.prototype.onWorkerComplete_ = function (frameState, err, output, data) { if (err || !output) { return; } // do nothing if extent or resolution changed var extent = frameState.extent; var resolution = frameState.viewState.resolution; if (resolution !== this.requestedFrameState_.viewState.resolution || !equals(extent, this.requestedFrameState_.extent)) { return; } var context; if (this.renderedImageCanvas_) { context = this.renderedImageCanvas_.getImage().getContext('2d'); } else { var width = Math.round(getWidth(extent) / resolution); var height = Math.round(getHeight(extent) / resolution); context = createCanvasContext2D(width, height); this.renderedImageCanvas_ = new ImageCanvas(extent, resolution, 1, context.canvas); } context.putImageData(output, 0, 0); this.changed(); this.renderedRevision_ = this.getRevision(); this.dispatchEvent(new RasterSourceEvent(RasterEventType.AFTEROPERATIONS, frameState, data)); }; /** * @override */ RasterSource.prototype.getImageInternal = function () { return null; // not implemented }; return RasterSource; }(ImageSource)); /** * A reusable canvas context. * @type {CanvasRenderingContext2D} * @private */ var sharedContext = null; /** * Get image data from a layer. * @param {import("../layer/Layer.js").default} layer Layer to render. * @param {import("../PluggableMap.js").FrameState} frameState The frame state. * @return {ImageData} The image data. */ function getImageData(layer, frameState) { var renderer = layer.getRenderer(); if (!renderer) { throw new Error('Unsupported layer type: ' + layer); } if (!renderer.prepareFrame(frameState)) { return null; } var width = frameState.size[0]; var height = frameState.size[1]; var container = renderer.renderFrame(frameState, null); var element; if (container) { element = container.firstElementChild; } if (!(element instanceof HTMLCanvasElement)) { throw new Error('Unsupported rendered element: ' + element); } if (element.width === width && element.height === height) { var context = element.getContext('2d'); return context.getImageData(0, 0, width, height); } if (!sharedContext) { sharedContext = createCanvasContext2D(width, height); } else { var canvas = sharedContext.canvas; if (canvas.width !== width || canvas.height !== height) { sharedContext = createCanvasContext2D(width, height); } else { sharedContext.clearRect(0, 0, width, height); } } sharedContext.drawImage(element, 0, 0, width, height); return sharedContext.getImageData(0, 0, width, height); } /** * Get a list of layer states from a list of layers. * @param {Array<import("../layer/Layer.js").default>} layers Layers. * @return {Array<import("../layer/Layer.js").State>} The layer states. */ function getLayerStatesArray(layers) { return layers.map(function (layer) { return layer.getLayerState(); }); } /** * Create layers for all sources. * @param {Array<import("./Source.js").default|import("../layer/Layer.js").default>} sources The sources. * @return {Array<import("../layer/Layer.js").default>} Array of layers. */ function createLayers(sources) { var len = sources.length; var layers = new Array(len); for (var i = 0; i < len; ++i) { layers[i] = createLayer(sources[i]); } return layers; } /** * Create a layer for the provided source. * @param {import("./Source.js").default|import("../layer/Layer.js").default} layerOrSource The layer or source. * @return {import("../layer/Layer.js").default} The layer. */ function createLayer(layerOrSource) { // @type {import("../layer/Layer.js").default} var layer; if (layerOrSource instanceof Source) { if (layerOrSource instanceof TileSource) { layer = new TileLayer({ source: layerOrSource }); } else if (layerOrSource instanceof ImageSource) { layer = new ImageLayer({ source: layerOrSource }); } } else { layer = layerOrSource; } return layer; } export default RasterSource; //# sourceMappingURL=Raster.js.map